长链剖分

将较长的链剖出来。

先来一道模板题

注意!!!

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

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;
}
原文地址:https://www.cnblogs.com/EricQian/p/15345145.html