bzoj2157旅游

题意:

给定有权树,支持单边权修改,路径边权取相反数,路径边权求和,路径边权求最大最小值。

题解:

用link-cut tree

link-cut tree与树链剖分有些类似,都是用某种数据结构维护树链。但也有很大差异:树链剖分是依据子树节点数确定轻重边,一经确定,不能更改,所以用相对静态的线段树维护,常数也较小。而link-cut tree是用来求解动态树问题的,它的链随时可以改变,因此也只能用动态的splay来维护,常数较大。以下简称lct

注意:本数据结构中splay是以节点在lct中的深度为关键字确定顺序的。lct中节点的fa有两个意思,当它不为自己所在splay的根结点时,fa表示其在splay中的父节点,当它为splay中的根节点时,fa表示这整条树链在lct中的父亲节点。

lct有三个基础操作:

1、access(u):使节点到根节点连成一条链,并把链上的分支断开。注意access过程就是不停的把节点旋到splay顶,将它与它的fa(即树链在lct中的fa)连接起来,因为是连接成一个splay,所以其fa节点的c[fa][1]也要修改并update
2、link(u,v):将u所在lct连到v所在lct。先access(u),再splay(u),此时u的fa就是u所在lct的fa,将u的fa置为v就行了
3、cut(u):将u从它所在的lct中脱离。先access(u),再splay(u),此时u的左子节点就是lct上它的实际父亲,将它切了就行。因为是涉及splay的切除,所以c[u][0]也要修改并update
可是有了这些怎么得到或更新任意两点间的信息呢?
我们知道,求两点间的信息一般都要求lca,lct怎么会没这个功能呢?求u,v的lca:先access(u),再access(v),后者在执行的时候最后得到那个t(见程序)就是lca,因为之前x已经和根节点相连了。两次access后,再splay(u),就形成一条从u连到lca且以u为根的树链,此时将u的信息与lca的右子节点信息合并即可。
吐槽:我是傻叉,rotate顺序写乱了,导致fa信息出错死循环re了3发,然后又因为两次access后没有splay又wa了2发,大样例调不出,自己的小样例又测不出错误,花了3h……
发现一个技巧,要让gdb在满足条件时停下来,可以if(条件满足)函数();然后在函数里设断点,就能准确中断了。
代码:

 #include <cstdio>
#include <cstring>
#include <algorithm>
#define N 20010
#define inc(i,j,k) for(int i=j;i<=k;i++)
#define INF 0x3fffffff
using namespace std; //general
int n,m;
void debug(){
n=n;
} //edge
struct e{int t,w,n;}; e es[N*]; int ess,g[N];
void pe(int f,int t,int w){es[++ess]=(e){t,w,g[f]}; g[f]=ess; es[++ess]=(e){f,w,g[t]}; g[t]=ess;} //splay
int fa[N]/*点的父亲,包括在splay中的父亲和所在splay的父亲*/,c[N][]/*splay中的儿子节点*/;
int sm[N],mx[N],mn[N],v[N],/*区间维护信息*/dt[N],dts/*中间过程用*/; bool tg[N]/*标记*/;
inline bool is_root(int x){return fa[x]==||(c[fa[x]][]!=x&&c[fa[x]][]!=x);}//判断是否为所在splay的根节点
void pushdown(int x){//标记下传。注:本程序中的标记表明本节点已更新子孙未更新
if(x==)return;
if(tg[x]){
int l=c[x][],r=c[x][]; tg[x]^=;
if(l){tg[l]^=; v[l]=-v[l]; sm[l]=-sm[l]; int t=mx[l]; mx[l]=-mn[l]; mn[l]=-t;}
if(r){tg[r]^=; v[r]=-v[r]; sm[r]=-sm[r]; int t=mx[r]; mx[r]=-mn[r]; mn[r]=-t;}
}
}
void update(int x){
if(x==)return;
int l=c[x][],r=c[x][]; sm[x]=v[x]+sm[l]+sm[r]; mx[x]=max(v[x],max(mx[l],mx[r])); mn[x]=min(v[x],min(mn[l],mn[r]));
}
void rotate(int x){//旋转
if(x==||is_root(x))return;
int a=fa[x],b=fa[fa[x]]; bool d=c[a][]==x,e=c[b][]==a;
if(! is_root(a))c[b][e]=x;//注意在调用is_root前不能动参数的fa[a]和c[fa[a]][0]c[fa[a]][1],不然会导致错误结果
if(c[x][!d])fa[c[x][!d]]=a;/*注意别漏*/ fa[x]=b; fa[a]=x; c[a][d]=c[x][!d]; c[x][!d]=a;//注意修改的相对顺序
update(a); update(x); if(! is_root(x))update(b);
}
void splay(int x){//伸展
if(x==)return;
dts=; int t=x;
while(! is_root(t))dt[++dts]=t,t=fa[t]; dt[++dts]=t;//将根结点到x的所有标记一次下传
while(dts)pushdown(dt[dts]),dts--;
while(! is_root(x)){
if(!is_root(fa[x])) (c[fa[x]][]==x)^(c[fa[fa[x]]][]==fa[x])?rotate(x):rotate(fa[x]);
rotate(x);
}
} //lct
int access(int x){//lct基本操作,使节点到根节点连成一条链,并把链上的分支断开
if(x==)return ;
int t=;
while(x){
splay(x); c[x][]=t; if(t)fa[t]=x;
update(x); t=x; x=fa[x];
}
debug();
return t;
}
void link(int x,int y){//lct基本操作,本程序不用
if(x==||y==)return;
access(x); splay(x); fa[x]=y;
}
void cut(int x){//lct基本操作,本程序不用
if(x==)return;
access(x); splay(x); if(c[x][])fa[c[x][]]=; c[x][]=; update(x);
} //command
//注意修改时要根据题目要求将修改查询边权变为程序中的修改查询点权
void change(int x,int val){//更新权值
splay(x); v[x]=val; update(x);
}
void rever(int x,int y){//反转
access(x); int a=access(y); /*别*/splay(x);/*漏!下同*/
if(x==a){
int r=c[x][]; tg[r]^=; v[r]=-v[r]; sm[r]=-sm[r]; int t=mx[r]; mx[r]=-mn[r]; mn[r]=-t; update(x);
}else{
tg[x]^=; v[x]=-v[x]; sm[x]=-sm[x]; int t=mx[x]; mx[x]=-mn[x]; mn[x]=-t;
int r=c[a][]; tg[r]^=; v[r]=-v[r]; sm[r]=-sm[r]; t=mx[r]; mx[r]=-mn[r]; mn[r]=-t;
update(a);
}
}
int querysum(int x,int y){//求和
access(x); int a=access(y); splay(x);
if(x==a)return sm[c[x][]];else return sm[x]+sm[c[a][]];
}
int querymax(int x,int y){//求最大值
access(x); int a=access(y); splay(x);
if(x==a)return mx[c[x][]];else return max(mx[x],mx[c[a][]]);
}
int querymin(int x,int y){//求最小值
access(x); int a=access(y); splay(x);
if(x==a)return mn[c[x][]];else return min(mn[x],mn[c[a][]]);
} int num[N];
void dfs(int x){//建树,并将边权转为点权 ,本程序用边的终点点权表示这条边的边权
for(int i=g[x];i!=-;i=es[i].n)if(es[i].t!=fa[x]){
fa[es[i].t]=x; v[es[i].t]=sm[es[i].t]=mx[es[i].t]=mn[es[i].t]=es[i].w; dfs(es[i].t);
}
}
int main(){
//freopen("big.txt","r",stdin); freopen("big.out","w",stdout);
memset(num,,sizeof(num)); ess=; memset(g,-,sizeof(g)); memset(fa,,sizeof(fa));
scanf("%d",&n); inc(i,,n-){int a,b,c; scanf("%d%d%d",&a,&b,&c); pe(a+,b+,c); num[i]=b+;}
dfs();
memset(c,,sizeof(c)); memset(tg,,sizeof(tg)); mn[]=INF; mx[]=-INF; scanf("%d",&m); char s[];
inc(i,,m){
int a,b; scanf("%s%d%d",s,&a,&b);
if(s[]=='C')change(num[a],b);
if(s[]=='N')a++,b++,rever(a,b);
if(s[]=='S')a++,b++,printf("%d\n",querysum(a,b));
if(s[]=='A')a++,b++,printf("%d\n",querymax(a,b));
if(s[]=='I')a++,b++,printf("%d\n",querymin(a,b));
}
return ;
}

20151215

-----------------------------------------------------------------------------------------------------

题解:

用树链剖分重写一下本题。树链剖分主要分3个步骤:

1、第一次dfs,求出各节点的子树大小、权值、深度等信息。

2、第二次dfs,求出各节点所在树链的头,以及各节点的中儿子、在所有树链依次相连组成的大链中的位置

3、将所有树链依次相连组成的大链建成一棵全局线段树

如何求lca(u,v)?

不停循环这样一个过程:u和v哪个深度大,就让它向上爬一条重链再爬一条轻边,直到两个所在链头节点相等(即在同一条树链)。此时,深度小的那个就是lca。

如何维护(查询)(u,v)之间信息?

求lca(u,v),然后对u不停循环这个过程:维护u到所在树链头的信息,接着u爬一条重链再一条轻边,直到树链头深度小于lca(此时u与lca在同一条树链),然后维护u到lca的信息。对v重复相同操作。

程序中我之所以存了每个节点的重儿子,是因为题意求边权,根据我的程序中边权与点权的转换方式,lca的权值不能被维护(查询),所以“维护u到lca的信息”就变成“维护u到lca重儿子的信息(如果此时u=lca,不能执行此操作)”

吐槽:线段树真耗空间,开的数组大小必须是点数的4倍!因为这个我re了5发。同时线段树的pushdown是边查询(修改)边执行的,需要注意。

对比链剖和link-cut tree,发现lct完胜!?不管在代码长度还是时间空间复杂度上都是lct更优。不是说线段树的常数比splay小得多吗?个人认为链剖输在一下几个方面:

1、链剖编程复杂度高,线段树要写一堆操作,链剖本身还要写一堆操作,同时链剖的操作还要写两个循环,大大增大了代码长度。

2、链剖空间复杂度高,4倍点数。这也是引起速度慢的一个重要原因,开大数组在一定程度上会增大程序的耗时。

总结:本题数据不是很大,链剖的适用范围应该是数据比较大的题,因为此时对程序本身常数的要求要高过空间引起的常数。

根本原因:我太弱了,肯定是我链剖写残才会这么慢!

代码:

 #include <cstdio>
#include <cstring>
#include <algorithm>
#define N 20010
#define inc(i,j,k) for(int i=j;i<=k;i++)
#define INF 0x3fffffff
using namespace std; //gerneral
int n,m; //edge
struct e{int t,w,n;}; e es[N*]; int ess,g[N];
inline void pe(int f,int t,int w){es[++ess]=(e){t,w,g[f]}; g[f]=ess; es[++ess]=(e){f,w,g[t]}; g[t]=ess;} //segment tree
int sm[N*],mx[N*],mn[N*]/*节点信息*/,v[N][]/*中间数组*/,l[N*],r[N*]/*区间*/;
int lc[N*],rc[N*]/*左右儿子*/; bool tg[N*]/*标记*/;
void update(int x){
if(x==)return;
sm[x]=sm[lc[x]]+sm[rc[x]]; mx[x]=max(mx[lc[x]],mx[rc[x]]); mn[x]=min(mn[lc[x]],mn[rc[x]]);
}
void pushdown(int x){//标记意义同lct
if(x==||!tg[x])return; int L=lc[x],R=rc[x]; tg[x]^=;
if(L){tg[L]^=; sm[L]=-sm[L]; int t=mx[L]; mx[L]=-mn[L]; mn[L]=-t;}
if(R){tg[R]^=; sm[R]=-sm[R]; int t=mx[R]; mx[R]=-mn[R]; mn[R]=-t;}
}
void build(int x,int L,int R){//建线段树
l[x]=L; r[x]=R;
if(L==R)sm[x]=mx[x]=mn[x]=v[L][],lc[x]=rc[x]=tg[x]=;else{
int M=(L+R)>>; lc[x]=x<<; rc[x]=x<<|; build(lc[x],L,M); build(rc[x],M+,R); tg[x]=; update(x);
}
}
void change(int x,int nod,int val){//线段树点修改
pushdown(x);
if(l[x]==r[x])sm[x]=mx[x]=mn[x]=val;else{
int M=(l[x]+r[x])>>; nod<=M?change(lc[x],nod,val):change(rc[x],nod,val); update(x);
}
}
void rever(int x,int ql,int qr){//线段树区间修改
pushdown(x);
int M=(l[x]+r[x])>>; if(ql<=l[x]&&r[x]<=qr){tg[x]^=; sm[x]=-sm[x]; int t=mx[x]; mx[x]=-mn[x]; mn[x]=-t; return;}
if(ql<=M)rever(lc[x],ql,qr); if(qr>M)rever(rc[x],ql,qr); update(x);
}
int querysum(int x,int ql,int qr){//线段树求和
pushdown(x);
int ret=,M=(l[x]+r[x])>>; if(ql<=l[x]&&r[x]<=qr)return sm[x];
if(ql<=M)ret+=querysum(lc[x],ql,qr); if(qr>M)ret+=querysum(rc[x],ql,qr);
return ret;
}
int querymax(int x,int ql,int qr){//线段树求最大值
pushdown(x);
int ret=-INF,M=(l[x]+r[x])>>; if(ql<=l[x]&&r[x]<=qr)return mx[x];
if(ql<=M)ret=max(ret,querymax(lc[x],ql,qr)); if(qr>M)ret=max(ret,querymax(rc[x],ql,qr));
return ret;
}
int querymin(int x,int ql,int qr){//线段树求最小值
pushdown(x);
int ret=INF,M=(l[x]+r[x])>>; if(ql<=l[x]&&r[x]<=qr)return mn[x];
if(ql<=M)ret=min(ret,querymin(lc[x],ql,qr)); if(qr>M)ret=min(ret,querymin(rc[x],ql,qr));
return ret;
} //tree chain apart
int pos[N]/*点在线段树中位置*/,top[N]/*树链头节点*/,fa[N],sz[N]/*子树大小*/,sgs,dep[N]/*深度*/,ps[N]/*重儿子*/;
void dfs(int x){//得到节点的父亲、子树大小、权值、深度
sz[x]=;
for(int i=g[x];i!=;i=es[i].n)if(es[i].t!=fa[x]){
fa[es[i].t]=x; dep[es[i].t]=dep[x]+; v[es[i].t][]=es[i].w; dfs(es[i].t); sz[x]+=sz[es[i].t];
}
}
void buildchain(int x,int tp/*当前节点所在树链头*/){//通过子树大小构造树链
pos[x]=++sgs; v[sgs][]=v[x][]; top[x]=tp; ps[x]=;
for(int i=g[x];i!=;i=es[i].n)if(es[i].t!=fa[x]){
if(sz[es[i].t]>sz[ps[x]])ps[x]=es[i].t;
}
if(ps[x]==)return;
buildchain(ps[x],tp);
for(int i=g[x];i!=;i=es[i].n)if(es[i].t!=fa[x]){
if(es[i].t!=ps[x])buildchain(es[i].t,es[i].t);
}
}
void init(){
memset(fa,,sizeof(fa)); dep[]=; dfs();
sgs=; mx[]=-INF; mn[]=INF; sz[]=; buildchain(,); build(,,sgs);
}
int lca(int x,int y){
for(;top[x]!=top[y];x=fa[top[x]]){if(dep[top[x]]<dep[top[y]])swap(x,y);}
return dep[x]<dep[y]?x:y;
}
void solvechange(int x,int val){change(,pos[x],val);}
void solverever(int x,int y){
if(x==y)return; int a=lca(x,y);
while(dep[top[x]]>dep[a])rever(,pos[top[x]],pos[x])/*注意参数顺序,下同*/,x=fa[top[x]];
if(a!=x)rever(,pos[ps[a]],pos[x]);
while(dep[top[y]]>dep[a])rever(,pos[top[y]],pos[y]),y=fa[top[y]];
if(a!=y)rever(,pos[ps[a]],pos[y]);
}
int solvesum(int x,int y){
if(x==y)return ; int a=lca(x,y),ans=;
while(dep[top[x]]>dep[a])ans+=querysum(,pos[top[x]],pos[x]),x=fa[top[x]];
if(a!=x)ans+=querysum(,pos[ps[a]],pos[x]);
while(dep[top[y]]>dep[a])ans+=querysum(,pos[top[y]],pos[y]),y=fa[top[y]];
if(a!=y)ans+=querysum(,pos[ps[a]],pos[y]);
return ans;
}
int solvemax(int x,int y){
if(x==y)return ; int a=lca(x,y),ans=-INF;
while(dep[top[x]]>dep[a])ans=max(ans,querymax(,pos[top[x]],pos[x])),x=fa[top[x]];
if(a!=x)ans=max(ans,querymax(,pos[ps[a]],pos[x]));
while(dep[top[y]]>dep[a])ans=max(ans,querymax(,pos[top[y]],pos[y])),y=fa[top[y]];
if(a!=y)ans=max(ans,querymax(,pos[ps[a]],pos[y]));
return ans;
}
int solvemin(int x,int y){
if(x==y)return ; int a=lca(x,y),ans=INF;
while(dep[top[x]]>dep[a])ans=min(ans,querymin(,pos[top[x]],pos[x])),x=fa[top[x]];
if(a!=x)ans=min(ans,querymin(,pos[ps[a]],pos[x]));
while(dep[top[y]]>dep[a])ans=min(ans,querymin(,pos[top[y]],pos[y])),y=fa[top[y]];
if(a!=y)ans=min(ans,querymin(,pos[ps[a]],pos[y]));
return ans;
} //main
int num[N];
int main(){
//freopen("zs.txt","r",stdin); freopen("zs.out","w",stdout);
scanf("%d",&n); memset(g,,sizeof(g)); ess=;
inc(i,,n-){int a,b,c; scanf("%d%d%d",&a,&b,&c); a++; b++; pe(a,b,c); num[i]=b;}
init(); scanf("%d",&m); char s[];
inc(i,,m){
int a,b; scanf("%s%d%d",s,&a,&b);
if(s[]=='C')solvechange(num[a],b);
if(s[]=='N')a++,b++,solverever(a,b);
if(s[]=='S')a++,b++,printf("%d\n",solvesum(a,b));
if(s[]=='A')a++,b++,printf("%d\n",solvemax(a,b));
if(s[]=='I')a++,b++,printf("%d\n",solvemin(a,b));
}
return ;
}

20151218

 

最新文章

  1. ajax实现上传文件
  2. arm64 boot
  3. C实现通用数据结构--双向链表
  4. nginx 的限制连接模块limit_zone与limit_req_zone
  5. 169. Majority Element My Submissions Question
  6. 【转】phpmyadmin万能密码漏洞
  7. poj 1681 Painter&#39;s Problem
  8. Android 中退出程序的几种方法
  9. JS正则替换字符串
  10. 使用docker搭建kafka环境
  11. Go语言学习笔记(四)结构体struct &amp; 接口Interface &amp; 反射
  12. vuethink 配置
  13. [Oracle] “表中有数据,但select count(*)的结果为0”问题的解决办法
  14. Java的Spring内实现的mini版内存&quot;计数器&quot;功能
  15. 制作基于U盘启动和网络常识
  16. 第一天进入博客这个神奇的领域 在此%%%erosun
  17. 转:UFLDL_Tutorial 笔记(deep learning绝佳的入门资料 )
  18. 安装 sql server 2008出现重启电脑,另在server 2012 r2安装sql server 2008 安装不上
  19. mysql数据库使用sql命令窗口查询的数据,改成sql语句导入到mysql数据库中
  20. C/C++——C语言数组名与指针

热门文章

  1. postman获得时间戳和md5加密的方法
  2. java并发编程系列原理篇--JDK中的通信工具类Semaphore
  3. WeChair项目Alpha冲刺(5/10)
  4. Apache Hudi:云数据湖解决方案
  5. C/C++语言的学习方向
  6. 重学 Java 设计模式:实战命令模式「模拟高档餐厅八大菜系,小二点单厨师烹饪场景」
  7. Linux下安装 Java
  8. tomcat中AJP协议和HTTP协议的区别
  9. Spring—容器外的Bean使用依赖注入
  10. 小师妹学JVM之:JIT中的PrintAssembly