BZOJ 3626 [LNOI2014]LCA 【树链剖分】
BZOJ 3626 [LNOI2014]LCA
Description
给出一个n个节点的有根树(编号为0到n-1,根节点为0)。一个点的深度定义为这个节点到根的距离+1。
设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先。
有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LCA(i,z)]。
(即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和)
Input
第一行2个整数n q。
接下来n-1行,分别表示点1到点n-1的父节点编号。
接下来q行,每行3个整数l r z。
Output
输出q行,每行表示一个询问的答案。每个答案对201314取模输出
Sample Input
5 2
0
0
1
1
1 4 3
1 4 2
0
0
1
1
1 4 3
1 4 2
Sample Output
8
5
5
HINT
共5组数据,n与q的规模分别为10000,20000,30000,40000,50000。
题解:
此题虽然标题是 LCA ,嘿嘿其实不然。。。
这题完全用不到 LCA,直接上树剖。
对于询问,我们这样操作:把 z 到根上的路径上的点权+1(初始点权为 0),然后对于 l 到 r 的点,向上一直到根的路径的点权和即为答案。(想一想,妙吧)
这样一来就方便了。我们把 l 到 r 的每个点到根节点的路径上的点权全 +1,最后求一遍 z 到根节点的路径点权之和就可以了。(好方便啊 ,%dalao)
然后对于 l 到 r ,我们用前缀和来处理。
此题完结!!!dalao喂!!!
本人代码太丑,贴上 hzwer 学长的条理清晰的代码以及我个人的注释:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #define ll long long 7 #define inf 1000000000 8 #define mod 201314 9 using namespace std; 10 // 快读 11 inline ll read() 12 { 13 ll x=0,f=1;char ch=getchar(); 14 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 15 while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 16 return x*f; 17 } 18 int n,m,cnt,place; 19 int bin[20]; 20 int son[50005],last[50005],fa[50005],belong[50005],pl[50005],deep[50005]; 21 struct edge{int to,next;}e[50005]; // 边 22 struct que{int z,ans1,ans2;}q[50005]; // 询问 23 struct data{int num,p;bool flag;}a[100005]; // 临时数组,区间从小到大排序 24 struct seg{int l,r,sum,tag,size;}t[200005]; // 线段树 25 // 排序,从小到大 26 inline bool operator<(data a,data b) 27 { 28 return a.p<b.p; 29 } 30 // 插入节点 31 inline void insert(int u,int v) 32 { 33 e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt; 34 } 35 // 第一次dfs,计算父亲节点 fa,深度 dep,以及子树的节点总数 size 36 void dfs1(int x) 37 { 38 son[x]=1; 39 for(int i=last[x];i;i=e[i].next) 40 { 41 if(e[i].to==fa[x])continue; 42 deep[e[i].to]=deep[x]+1; 43 fa[e[i].to]=x; 44 dfs1(e[i].to); 45 son[x]+=son[e[i].to]; 46 } 47 } 48 // 第二次dfs, 树链剖分 49 void dfs2(int x,int chain) 50 { 51 belong[x]=chain;pl[x]=++place; //belong 表示这根链的头, 52 // pl 表示所有重链被拉成一条时的新的标号 53 int k=n; 54 for(int i=last[x];i;i=e[i].next) 55 if(e[i].to!=fa[x]&&son[e[i].to]>son[k]) 56 k=e[i].to; // 找到重点 57 if(k!=n)dfs2(k,chain); // 先dfs重链, 58 for(int i=last[x];i;i=e[i].next) 59 if(e[i].to!=fa[x]&&e[i].to!=k) 60 dfs2(e[i].to,e[i].to); // 再dfs轻点,重新拉一条重链, 61 // 一定要把一条链dfs完,再dfs另一条,这样才能保证后来拉成一条序列中连续的一段在同一条重链上 62 } 63 // 线段树懒标记下传 64 inline void pushdown(int k) 65 { 66 if(t[k].l==t[k].r||!t[k].tag)return; 67 int tag=t[k].tag;t[k].tag=0; 68 t[k<<1].sum=t[k<<1].sum+t[k<<1].size*tag; // sum 更新 ,子节点数*tag 69 t[k<<1|1].sum=t[k<<1|1].sum+t[k<<1|1].size*tag; 70 t[k<<1].tag=t[k<<1].tag+tag; // 懒标记叠加 71 t[k<<1|1].tag=t[k<<1|1].tag+tag; 72 } 73 // 线段树建树过程 74 void build(int k,int l,int r) 75 { 76 t[k].l=l;t[k].r=r;t[k].size=r-l+1; //k表示线段树的节点编号 77 if(l==r)return; 78 int mid=(l+r)>>1; 79 build(k<<1,l,mid); 80 build(k<<1|1,mid+1,r); 81 } 82 // 区间更新 83 void update(int k,int x,int y) 84 { 85 pushdown(k); 86 int l=t[k].l,r=t[k].r; 87 if(l==x&&y==r) // 如果刚好满足,(区间边界相同)则直接统计答案 88 { 89 t[k].tag++;t[k].sum+=t[k].size; 90 return; 91 } 92 // 否则继续递归 93 int mid=(l+r)>>1; 94 if(y<=mid)update(k<<1,x,y); // 递归左子树,右子树不用 95 else if(x>mid)update(k<<1|1,x,y); // 递归右子树,左子树不用 96 else 97 { 98 update(k<<1,x,mid); 99 update(k<<1|1,mid+1,y); 100 } 101 t[k].sum=t[k<<1].sum+t[k<<1|1].sum; //父节点sum=sum左+sum右 102 } 103 // 跳重链过程 104 void solve_update(int x,int f) 105 { 106 while(belong[x]!=belong[f]) //如果不属于同一条重链 107 { 108 update(1,pl[belong[x]],pl[x]); // 更新所在重链的信息,从当前位置到头 109 x=fa[belong[x]]; // 把x跳到这条重链的头的父亲上,这样就跳到另一条重链上了 110 } 111 update(1,pl[f],pl[x]); 112 } 113 // 区间求值 114 int query(int k,int x,int y) 115 { 116 pushdown(k); 117 int l=t[k].l,r=t[k].r; 118 if(l==x&&y==r)return t[k].sum; 119 int mid=(l+r)>>1; 120 if(y<=mid)return query(k<<1,x,y); 121 else if(x>mid)return query(k<<1|1,x,y); 122 else return query(k<<1,x,mid)+query(k<<1|1,mid+1,y); 123 } 124 // 跳重链过程,同上 125 int solve_query(int x,int f) 126 { 127 int sum=0; 128 while(belong[x]!=belong[f]) 129 { 130 sum+=query(1,pl[belong[x]],pl[x]); 131 sum%=mod; 132 x=fa[belong[x]]; 133 } 134 sum+=query(1,pl[f],pl[x]);sum%=mod; 135 return sum; 136 //sum的求和 满足剩余定理,直接在做的过程中 % 137 } 138 int main() 139 { 140 //freopen("lca.in","r",stdin); 141 //freopen("lca.out","w",stdout); 142 bin[0]=1;for(int i=1;i<20;i++)bin[i]=(bin[i-1]<<1); 143 n=read();m=read(); 144 for(int i=1;i<n;i++) 145 { 146 int x=read();insert(x,i); 147 } 148 int tot=0; 149 for(int i=1;i<=m;i++) 150 { 151 int l=read(),r=read(); 152 q[i].z=read(); 153 a[++tot].p=l-1;a[tot].num=i;a[tot].flag=0; 154 a[++tot].p=r;a[tot].num=i;a[tot].flag=1; 155 // a数组为临时数组,用来离线将区间排序, 156 // flag 表示是左端点还是右端点,num 表示询问的编号,p为端点 157 } 158 build(1,1,n); 159 sort(a+1,a+tot+1); // 从小到大排序 160 dfs1(0);dfs2(0,0); 161 int now=-1; 162 for(int i=1;i<=tot;i++) 163 { 164 while(now<a[i].p) // 有点像莫队操作,左端点移动,并将左端点之前的节点清空贡献 165 { 166 now++; 167 solve_update(now,0); 168 } 169 int t=a[i].num; 170 if(!a[i].flag)q[t].ans1=solve_query(q[t].z,0); // 求前缀和 171 else q[t].ans2=solve_query(q[t].z,0); 172 } 173 for(int i=1;i<=m;i++) 174 printf("%d\n",(q[i].ans2-q[i].ans1+mod)%mod); 175 return 0; 176 }
树剖代码长,往往要结合数据结构,这种题要多练,才能写熟练!!!
加油加油加油!!!fighting fighting fighting!!!