题目:
2157: 旅游
Time Limit: 10 Sec Memory Limit: 259 MB
Submit: 569 Solved: 310
[Submit][Status][Discuss]
Description
Ray 乐忠于旅游,这次他来到了T 城。T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接。为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有一条路径。换句话说, T 城中只有N − 1 座桥。Ray 发现,有些桥上可以看到美丽的景色,让人心情愉悦,但有些桥狭窄泥泞,令人烦躁。于是,他给每座桥定义一个愉悦度w,也就是说,Ray 经过这座桥会增加w 的愉悦度,这或许是正的也可能是负的。有时,Ray 看待同一座桥的心情也会发生改变。现在,Ray 想让你帮他计算从u 景点到v 景点能获得的总愉悦度。有时,他还想知道某段路上最美丽的桥所提供的最大愉悦度,或是某段路上最糟糕的一座桥提供的最低愉悦度。
Input
输入的第一行包含一个整数N,表示T 城中的景点个数。景点编号为 0...N − 1。接下来N − 1 行,每行三个整数u、v 和w,表示有一条u 到v,使 Ray 愉悦度增加w 的桥。桥的编号为1...N − 1。|w| <= 1000。输入的第N + 1 行包含一个整数M,表示Ray 的操作数目。接下来有M 行,每行描述了一个操作,操作有如下五种形式: C i w,表示Ray 对于经过第i 座桥的愉悦度变成了w。 N u v,表示Ray 对于经过景点u 到v 的路径上的每一座桥的愉悦度都变成原来的相反数。 SUM u v,表示询问从景点u 到v 所获得的总愉悦度。 MAX u v,表示询问从景点u 到v 的路径上的所有桥中某一座桥所提供的最大愉悦度。 MIN u v,表示询问从景点u 到v 的路径上的所有桥中某一座桥所提供的最小愉悦度。测试数据保证,任意时刻,Ray 对于经过每一座桥的愉悦度的绝对值小于等于1000。
Output
对于每一个询问(操作S、MAX 和MIN),输出答案。
样例是2000行的大数据不给了
2015.12.15:
程序:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define N 20010 5 #define inc(i,j,k) for(int i=j;i<=k;i++) 6 #define INF 0x3fffffff 7 using namespace std; 8 9 //general 10 int n,m; 11 void debug(){ 12 n=n; 13 } 14 15 //edge 16 struct e{int t,w,n;}; e es[N*2]; int ess,g[N]; 17 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;} 18 19 //splay 20 int fa[N]/*点的父亲,包括在splay中的父亲和所在splay的父亲*/,c[N][2]/*splay中的儿子节点*/; 21 int sm[N],mx[N],mn[N],v[N],/*区间维护信息*/dt[N],dts/*中间过程用*/; bool tg[N]/*标记*/; 22 inline bool is_root(int x){return fa[x]==0||(c[fa[x]][0]!=x&&c[fa[x]][1]!=x);}//判断是否为所在splay的根节点 23 void pushdown(int x){//标记下传。注:本程序中的标记表明本节点已更新子孙未更新 24 if(x==0)return; 25 if(tg[x]){ 26 int l=c[x][0],r=c[x][1]; tg[x]^=1; 27 if(l){tg[l]^=1; v[l]=-v[l]; sm[l]=-sm[l]; int t=mx[l]; mx[l]=-mn[l]; mn[l]=-t;} 28 if(r){tg[r]^=1; v[r]=-v[r]; sm[r]=-sm[r]; int t=mx[r]; mx[r]=-mn[r]; mn[r]=-t;} 29 } 30 } 31 void update(int x){ 32 if(x==0)return; 33 int l=c[x][0],r=c[x][1]; 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])); 34 } 35 void rotate(int x){//旋转 36 if(x==0||is_root(x))return; 37 int a=fa[x],b=fa[fa[x]]; bool d=c[a][1]==x,e=c[b][1]==a; 38 if(! is_root(a))c[b][e]=x;//注意在调用is_root前不能动参数的fa[a]和c[fa[a]][0]c[fa[a]][1],不然会导致错误结果 39 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;//注意修改的相对顺序 40 update(a); update(x); if(! is_root(x))update(b); 41 } 42 void splay(int x){//伸展 43 if(x==0)return; 44 dts=0; int t=x; 45 while(! is_root(t))dt[++dts]=t,t=fa[t]; dt[++dts]=t;//将根结点到x的所有标记一次下传 46 while(dts)pushdown(dt[dts]),dts--; 47 while(! is_root(x)){ 48 if(!is_root(fa[x])) (c[fa[x]][1]==x)^(c[fa[fa[x]]][1]==fa[x])?rotate(x):rotate(fa[x]); 49 rotate(x); 50 } 51 } 52 53 //lct 54 int access(int x){//lct基本操作,使节点到根节点连成一条链,并把链上的分支断开 55 if(x==0)return 0; 56 int t=0; 57 while(x){ 58 splay(x); c[x][1]=t; if(t)fa[t]=x; 59 update(x); t=x; x=fa[x]; 60 } 61 debug(); 62 return t; 63 } 64 void link(int x,int y){//lct基本操作,本程序不用 65 if(x==0||y==0)return; 66 access(x); splay(x); fa[x]=y; 67 } 68 void cut(int x){//lct基本操作,本程序不用 69 if(x==0)return; 70 access(x); splay(x); if(c[x][0])fa[c[x][0]]=0; c[x][0]=0; update(x); 71 } 72 73 //command 74 //注意修改时要根据题目要求将修改查询边权变为程序中的修改查询点权 75 void change(int x,int val){//更新权值 76 splay(x); v[x]=val; update(x); 77 } 78 void rever(int x,int y){//反转 79 access(x); int a=access(y); /*别*/splay(x);/*漏!下同*/ 80 if(x==a){ 81 int r=c[x][1]; tg[r]^=1; v[r]=-v[r]; sm[r]=-sm[r]; int t=mx[r]; mx[r]=-mn[r]; mn[r]=-t; update(x); 82 }else{ 83 tg[x]^=1; v[x]=-v[x]; sm[x]=-sm[x]; int t=mx[x]; mx[x]=-mn[x]; mn[x]=-t; 84 int r=c[a][1]; tg[r]^=1; v[r]=-v[r]; sm[r]=-sm[r]; t=mx[r]; mx[r]=-mn[r]; mn[r]=-t; 85 update(a); 86 } 87 } 88 int querysum(int x,int y){//求和 89 access(x); int a=access(y); splay(x); 90 if(x==a)return sm[c[x][1]];else return sm[x]+sm[c[a][1]]; 91 } 92 int querymax(int x,int y){//求最大值 93 access(x); int a=access(y); splay(x); 94 if(x==a)return mx[c[x][1]];else return max(mx[x],mx[c[a][1]]); 95 } 96 int querymin(int x,int y){//求最小值 97 access(x); int a=access(y); splay(x); 98 if(x==a)return mn[c[x][1]];else return min(mn[x],mn[c[a][1]]); 99 } 100 101 int num[N]; 102 void dfs(int x){//建树,并将边权转为点权 ,本程序用边的终点点权表示这条边的边权 103 for(int i=g[x];i!=-1;i=es[i].n)if(es[i].t!=fa[x]){ 104 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); 105 } 106 } 107 int main(){ 108 //freopen("big.txt","r",stdin); freopen("big.out","w",stdout); 109 memset(num,0,sizeof(num)); ess=0; memset(g,-1,sizeof(g)); memset(fa,0,sizeof(fa)); 110 scanf("%d",&n); inc(i,1,n-1){int a,b,c; scanf("%d%d%d",&a,&b,&c); pe(a+1,b+1,c); num[i]=b+1;} 111 dfs(1); 112 memset(c,0,sizeof(c)); memset(tg,0,sizeof(tg)); mn[0]=INF; mx[0]=-INF; scanf("%d",&m); char s[10]; 113 inc(i,1,m){ 114 int a,b; scanf("%s%d%d",s,&a,&b); 115 if(s[0]==‘C‘)change(num[a],b); 116 if(s[0]==‘N‘)a++,b++,rever(a,b); 117 if(s[0]==‘S‘)a++,b++,printf("%d\n",querysum(a,b)); 118 if(s[1]==‘A‘)a++,b++,printf("%d\n",querymax(a,b)); 119 if(s[1]==‘I‘)a++,b++,printf("%d\n",querymin(a,b)); 120 } 121 return 0; 122 }
题解:用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(条件满足)函数();然后在函数里设断点,就能准确中断了。