• 长链剖分


    将较长的链剖出来。

    先来一道模板题

    注意!!!

    【指针版长链剖分】循环遍历儿子们的答案时,

    for(int j=0;j<len[ver[i]];j++)...
    

    而不是(因为申请了长度为 (len) 的数组!!)

    for(int j=0;j<=len[ver[i]];j++)...
    

    【模板】树上 k 级祖先

    支持 (O(1)) 查询 (k) 级祖先,预处理是 (O(nlog n)) 的。

    $ exttt{code}$
    // Author:A weak man named EricQian
    #include<bits/stdc++.h>
    using namespace std;
    #define inf 0x3f3f3f3f
    #define Maxn 500005
    typedef long long ll;
    typedef unsigned int ui;
    inline int rd()
    {
    	 int x=0;
         char ch,t=0;
         while(!isdigit(ch = getchar())) t|=ch=='-';
         while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
         return x=t?-x:x;
    }
    int n,q,s,tot,root;
    ll ans,Last;
    int fa[Maxn][22],g[Maxn];
    int maxdep[Maxn],dep[Maxn],lson[Maxn],tp[Maxn];
    int hea[Maxn],nex[Maxn],ver[Maxn];
    vector<int> Up[Maxn],Down[Maxn];
    inline ui get(ui x)
    {
    	x ^= x << 13,x ^= x >> 17,x ^= x << 5;
    	return s = x; 
    }
    inline void add(int x,int y){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot; }
    void dfs1(int x)
    {
    	 for(int i=hea[x];i;i=nex[i])
    	 {
    	 	 maxdep[ver[i]]=dep[ver[i]]=dep[x]+1,fa[ver[i]][0]=x;
    	 	 for(int j=1;j<=20;j++) fa[ver[i]][j]=fa[fa[ver[i]][j-1]][j-1];
    	 	 dfs1(ver[i]);
    	 	 if(maxdep[ver[i]]>maxdep[lson[x]]) lson[x]=ver[i],maxdep[x]=maxdep[ver[i]];
    	 }
    }
    void dfs2(int x,int T)
    {
    	 tp[x]=T;
    	 if(x==T) 
    	 {
    	 	 for(int i=maxdep[x]-dep[x],tmp=x;i>=0;i--)
    	 	 	 Up[x].push_back(tmp),tmp=fa[tmp][0];
    	 	 for(int i=maxdep[x]-dep[x],tmp=x;i>=0;i--)
    	 	 	 Down[x].push_back(tmp),tmp=lson[tmp];
    	 }
    	 if(lson[x]) dfs2(lson[x],T);
    	 for(int i=hea[x];i;i=nex[i]) if(ver[i]!=lson[x]) dfs2(ver[i],ver[i]);
    }
    ll query(int x,int k)
    {
    	 if(!k) return x;
    	 x=fa[x][g[k]],k-=(1<<g[k]);
    	 k-=dep[x]-dep[tp[x]];
    	 // 跳到链顶端,若有,则继续向上跳;若无,说明在这条链中 
    	 x=tp[x];
    	 return (k>=0)?Up[x][k]:Down[x][-k];
    }
    int main()
    {
         //freopen(".in","r",stdin);
         //freopen(".out","w",stdout);
    	 n=rd(),q=rd(),s=rd();
    	 for(int i=1,fa;i<=n;i++) fa=rd(),add(fa,i),root=(!fa)?i:root;
    	 g[0]=-1; for(int i=1;i<=n;i++) g[i]=g[i>>1]+1;
    	 dep[root]=1,dfs1(root),dfs2(root,root);
    	 for(int i=1,x,k;i<=q;i++)
    	 {
    	 	 x=(get(s)^Last)%n+1;
    	 	 k=(get(s)^Last)%dep[x];
    	 	 Last=query(x,k);
    	 	 ans^=1ll*i*Last;
    	 }
    	 printf("%lld
    ",ans);
         //fclose(stdin);
         //fclose(stdout);
         return 0;
    }
    

    然而大多数情况下,长链剖分都是用指针实现的,下面就是一个比较简单的例子。

    CF1009F Dominant Indices

    我们发现在同一条长链中,我们不必每次将整个数组转移,只用将长儿子的深度 (+1) 边可以得到父亲在这个儿子上的答案,因此采用 数组公用空间 来实现 (+1) 的操作。

    具体来说就是用指针实现数组的移位。

    比如我们进行了如下的定义与赋值:

    int *f[Maxn],tmp[Maxn],*o=tmp;
    
    o+=1,f[1]=o;
    

    那么,我们可以直接访问 f[1][0] ,但是 f[1][0] 实际上存储在了 tmp[1] 的位置。

    $ exttt{code}$
    // Author:A weak man named EricQian
    #include<bits/stdc++.h>
    using namespace std;
    #define inf 0x3f3f3f3f
    #define Maxn 1000005
    typedef long long ll;
    inline int rd()
    {
    	 int x=0;
         char ch,t=0;
         while(!isdigit(ch = getchar())) t|=ch=='-';
         while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
         return x=t?-x:x;
    }
    int n,tot;
    int ans[Maxn],lson[Maxn],len[Maxn]; // 从下往上记录,表示这个点能往下走的距离。
    int hea[Maxn],nex[Maxn<<1],ver[Maxn<<1];
    int *f[Maxn],tmp[Maxn],*o=tmp;
    inline void add(int x,int y){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot; }
    void dfs1(int x,int fa)
    {
    	 for(int i=hea[x];i;i=nex[i])
    	 {
    	 	 if(ver[i]==fa) continue;
    	 	 dfs1(ver[i],x);
    	 	 if(len[ver[i]]>len[lson[x]]) lson[x]=ver[i];
    	 }
    	 len[x]=len[lson[x]]+1;
    }
    void dfs2(int x,int fa)
    {
    	 f[x][0]=1;
    	 if(lson[x]) f[lson[x]]=f[x]+1,dfs2(lson[x],x),ans[x]=ans[lson[x]]+1;
    	 if(f[x][ans[x]]<=1) ans[x]=0;
    	 for(int i=hea[x];i;i=nex[i])
    	 {
    	 	 if(ver[i]==fa || ver[i]==lson[x]) continue;
    	 	 f[ver[i]]=o,o+=len[ver[i]];
    	 	 dfs2(ver[i],x);
    	 	 for(int j=1;j<=len[ver[i]];j++)
    	 	 {
    	 	 	 f[x][j]+=f[ver[i]][j-1];
    	 	 	 if(f[x][j]>f[x][ans[x]] || (f[x][j]==f[x][ans[x]] && j<ans[x]))
    	 	 	 	 ans[x]=j;
    		 }
    	 }
    }
    int main()
    {
    	 //ios::sync_with_stdio(false); cin.tie(0);
         //freopen(".in","r",stdin);
         //freopen(".out","w",stdout);
    	 n=rd();
    	 for(int i=1,x,y;i<n;i++) x=rd(),y=rd(),add(x,y),add(y,x);
    	 dfs1(1,0);
    	 f[1]=o,o+=len[1];
    	 dfs2(1,0);
    	 for(int i=1;i<=n;i++) printf("%d
    ",ans[i]);
         //fclose(stdin);
         //fclose(stdout);
         return 0;
    }
    

    再来一个较为复杂的例子。

    P5904 [POI2014]HOT-Hotels 加强版

    这一题的转移方程就比较难以想到,我们设:

    • (f(i,j)) 表示在 (i) 为根的子树中,距离 (i)(j) 的子树有多少个

    • (g(i,j)) 表示在 (i) 为根的子树中,满足 (dist(i,operatorname{lca}(x,y))+j=dist(x,operatorname{lca}(x,y))=dist(y,operatorname{lca}(x,y))) 的点对有多少个。

    有这样的转移:

    [ans leftarrow g_{i, 0} ]

    [ans leftarrow sum_{x,y in operatorname{son}(i), x e y} f_{x,j-1} imes g_{y,j+1} ]

    [g_{i,j} leftarrow sum_{x,y in operatorname{son}(i), x e y} f_{x,j-1} imes f_{y,j-1} ]

    [g_{i,j} leftarrow sum_{x in operatorname{son}(i)} g_{x, j+1} ]

    [f_{i,j} leftarrow sum_{x in operatorname{son}(i)} f_{x, j-1} ]

    因此我们可以用 (f(i,j)) 的前缀和优化,(g(i,j)) 的后缀和优化来实现 (O(n)) 之内的转移。

    又发现这个转移方程都是以深度为下标的,所以可以利用上指针优化的长链剖分实现均摊 (O(n))

    $ exttt{code}$
    #define infll 0x7f7f7f7f7f7f7f7f
    #define inf 0x3f3f3f3f
    #define Maxn 500005
    typedef long long ll;
    inline int rd()
    {
    	 int x=0;
         char ch,t=0;
         while(!isdigit(ch = getchar())) t|=ch=='-';
         while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
         return x=t?-x:x;
    }
    int n,tot;
    int len[Maxn],lson[Maxn],hea[Maxn],nex[Maxn<<1],ver[Maxn<<1];
    ll ans,*f[Maxn],*g[Maxn],tmp[Maxn<<1],*o=tmp;
    inline void add(int x,int y){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot; }
    void dfs1(int x,int fa)
    {
    	 for(int i=hea[x];i;i=nex[i])
    	 {
    	 	 if(ver[i]==fa) continue;
    	 	 dfs1(ver[i],x);
    	 	 if(len[ver[i]]>len[lson[x]]) lson[x]=ver[i];
    	 }
    	 len[x]=len[lson[x]]+1;
    }
    void dfs2(int x,int fa)
    {
    	 if(lson[x]) f[lson[x]]=f[x]+1,g[lson[x]]=g[x]-1,dfs2(lson[x],x);
    	 f[x][0]=1,ans+=g[x][0];
    	 for(int i=hea[x];i;i=nex[i])
    	 {
    	 	 if(ver[i]==fa || ver[i]==lson[x]) continue;
    	 	 f[ver[i]]=o,o+=len[ver[i]]<<1;
    	 	 g[ver[i]]=o,o+=len[ver[i]]<<1;
    	 	 dfs2(ver[i],x);
    	 	 for(int j=0;j<=len[ver[i]];j++)
    	 	 {
    	 	 	 if(j) ans+=f[x][j-1]*g[ver[i]][j];
    	 	 	 ans+=g[x][j+1]*f[ver[i]][j]; // 从前、后利用前缀和计算 
    		 }
    		 for(int j=0;j<=len[ver[i]];j++)
    		 {
    		 	 g[x][j+1]+=f[x][j+1]*f[ver[i]][j]; // 利用前缀和累加答案 
    		 	 if(j) g[x][j-1]+=g[ver[i]][j]; // 更新从后往前的前缀和 
    		 	 f[x][j+1]+=f[ver[i]][j]; // 更新从前往后的前缀和 
    		 }
    	 }
    }
    int main()
    {
    	 n=rd();
    	 for(int i=1,x,y;i<n;i++) x=rd(),y=rd(),add(x,y),add(y,x);
    	 dfs1(1,0);
    	 f[1]=o,o+=len[1]<<1;
    	 g[1]=o,o+=len[1]<<1;
    	 dfs2(1,0);
    	 printf("%lld
    ",ans);
         return 0;
    }
    
  • 相关阅读:
    eslint 规则
    我的.eslintrc.js
    shell命令
    .sync 修饰符的理解
    【HNOI 2018】寻宝游戏
    【BZOJ 2820】YY的GCD
    【Luogu P2664】树上游戏
    【HAOI 2012】高速公路
    句摘
    【SCOI 2008】奖励关
  • 原文地址:https://www.cnblogs.com/EricQian/p/15345145.html
Copyright © 2020-2023  润新知