• 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!!!

  • 相关阅读:
    LeetCode--Insert Interval
    LeetCode--Surrounded Regions
    LeetCode--Container With Most Water
    LeetCode--Permutation Sequence
    NYOJ---540奇怪的排序
    NYOJ-858下三角矩阵
    素数筛选法
    NYOJ----776删除元素
    矩阵快速幂
    神奇算式
  • 原文地址:https://www.cnblogs.com/Frank-King/p/9328599.html
Copyright © 2020-2023  润新知