@XLM
2017-09-08T03:05:03.000000Z
字数 3500
阅读 324
学习 图论 树结构
总是有这样或者那样找事的题目
在题目里,我们要在树上不断地修改路径,查询路径
就算这是一棵树,如果不做任何处理,修改O(1),查询O(depth(u)+depth(v))。
这种复杂度是我们无法承受的。
不如树剖
树剖,就是树链剖分。顾名思义,就是将树以轻重链方式分开
Q:诶诶,分开干什么啊好好的树多可爱啊
A: 可爱可是不经cha啊
口述无凭,不如看道题
我们要搞事情(MC)啦(并不)
看完题回来了吗?如果你还不想MC想搞点更有意义滴事情你就往下看
不多说,都在酒代码里
#include<cstdio>#include<cmath>#include<iostream>#include<cstring>#include<algorithm>#include<vector>//#define XLMusing namespace std;const int maxn=10010;/*树链剖分是通过将树上边通过不重不漏的方式hash到线段树上从而方便地进行查询的方法。比如,我们想要查询x--y之间的路径上的边(当然,点也可以)的权值的最小值(或最大值),这时候如果暴力的先求出LCA再按边查询,这样复杂度最坏便是O(depth[x]+depth[y]),复杂度远远超出一般要求(O(logn))这时我们的想法便是:可不可以将一些边放到一起查询?此时将树分为轻重链的树剖应运而生*///树剖部分struct tree_edge{int x,y,val;void read(){scanf("%d%d%d",&x,&y,&val);}};tree_edge e[maxn];//上面是用来存树边的结构体,用来方便对于每一条边的查询以及一会的建树vector <int> G[maxn];//存图int dep[maxn],id[maxn],son[maxn],val[maxn],top[maxn],siz[maxn],fa[maxn];//dep:每个节点的深度 id:即dfn—节点的dfs序 son:节点的重儿子的编号 fa:节点父节点的编号//val:存取关于节点的权值的信息以助于线段树查询 top:一条重链最上端的节点的编号//siz:以一个节点为根节点向下的子树的节点数void dfs_1(int u,int f,int depth){//第一次DFS,目的为了求出dep,siz,fa,sondep[u]=depth;siz[u]=1;son[u]=0;fa[u]=f;//记录当前节点状态for (int i=0;i<G[u].size();i++){int to=G[u][i];if (to==f) continue;//保证不回头dfs_1(to,u,depth+1);//递归查询子节点siz[u]+=siz[to];if (siz[son[u]]<siz[to]) son[u]=to;//重儿子就是儿子中siz数最大的儿子,因此枚举每一个儿子就找到了}}/*我们在树链剖分中,第一次的dfs是为了求出一个节点的位置信息和儿子父亲关系。经过第一次的dfs我们找出了重儿子,现在进行第二次dfs,用来找重链。* 重链是什么?就是节点和其重儿子,重儿子的重儿子等……以及他们之间的边组成的链。我们把单独没有重儿子的叶子节点也叫做一条重链* 这些重链能够保证每个节点都被其所覆盖,而且保证不重复覆盖*/int cnt=0;//dfs_clockvoid dfs_2(int u,int tp){//第二次dfs,目的求出top和id数组top[u]=tp;id[u]=++cnt;if (son[u]) dfs_2(son[u],tp);//如果该节点有重儿子,证明该节点的重儿子与该节点在同一条重链上for (int i=0;i<G[u].size();i++){//进行轻儿子(与重儿子相对)的访问,即访问处理一条新链int to=G[u][i];if (to==fa[u]||to==son[u]) continue;//防止走回头路以及访问到重儿子dfs_2(to,to);}}void init(int n){//预处理出边for (int i=1;i<=n;i++){if (dep[e[i].x] < dep[e[i].y]) swap(e[i].x,e[i].y);val[id[e[i].x]]=e[i].val;}}//那么为什么我们经过这样处理的边就能够通过线段树查询呢//首先,我们在DFS时采取了优先查询每一个点的重儿子,这让一条重链上的所有点的DFN必定是连续的//这样就产生了一个效果,就是说我们一条重链上的所有点的编号,都是连续的//既然你连续了,我就勉为其难的用线段树cha你好啦//线段树部分,此部分不再加叙述struct segment{int l,r,val;};segment seg[4*maxn+10];void pushup(int x){seg[x].val=max(seg[x<<1].val,seg[x<<1|1].val);}void build(int l,int r,int o){seg[o].l=l,seg[o].r=r;if (l==r){seg[o].val=val[l];return;}int mid=(l+r)>>1;build(l,mid,o<<1);build(mid+1,r,o<<1|1);pushup(o);}void update(int pos,int x,int o){int l=seg[o].l,r=seg[o].r;if (l==r){seg[o].val=x;return;}int mid=(l+r)>>1;if (pos<=mid) update(pos,x,o<<1);if (pos>mid) update(pos,x,o<<1|1);pushup(o);}int query(int L,int R,int o){int l=seg[o].l,r=seg[o].r;if (L<=l&&r<=R) return seg[o].val;int ans=0,mid=(l+r)>>1;if (L<=mid) ans=max(ans,query(L,R,o<<1));if (R>mid) ans=max(ans,query(L,R,o<<1|1));return ans;}void Clear(int n){for (int i=1;i<=n;i++) G[i].clear();}/*树链剖分的查询部分* 我们现在有两个点查询他们之间的信息* 因为他们之间的路径是唯一的(树的性质),那我们不如查询u到LCA和v到LCA*/int Qtree(int u,int v){int top_1=top[u],top_2=top[v];int ans=0;while (top_1!=top_2){if (dep[top_1] < dep[top_2]) swap(top_1,top_2),swap(u,v);//调换顺序方便查询ans=max(ans,query(id[top_1],id[u],1));//查询u到u重链的顶端u=fa[top[u]],top_1=top[u];}if (u==v) return ans;//如果他们在一条重链上,我们就查完了if (dep[u] > dep[v]) swap(u,v);ans=max(ans,query(id[son[u]],id[v],1));// 如果没查完,那现在两个点必是父亲儿子关系,更简单了return ans;}int main(){freopen("qtree.in","r",stdin);freopen("qtree.out","w",stdout);int t;t=1;while (t--){int n;scanf("%d",&n);for (int i=1;i<n;i++){e[i].read();G[e[i].x].push_back(e[i].y);G[e[i].y].push_back(e[i].x);}cnt=0;dfs_1(1,0,1);dfs_2(1,1);init(n);build(1,cnt,1);char buf[100];while (~scanf("%s",buf) && buf[0]!='D'){int x,y;scanf("%d%d",&x,&y);if (buf[0]=='Q') printf("%d\n",Qtree(x,y));if (buf[0]=='C') update(id[e[x].x],y,1);}Clear(n);//记得清空数组}return 0;}