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

Sample Output

8
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 学长的条理清晰的代码以及我个人的注释:

  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
",(q[i].ans2-q[i].ans1+mod)%mod);
175     return 0;
176 }
View Code

树剖代码长,往往要结合数据结构,这种题要多练,才能写熟练!!!

 加油加油加油!!!fighting fighting fighting!!!

原文地址:https://www.cnblogs.com/Frank-King/p/9328599.html