长链剖分:O(nlogn)预处理O(1)求kth祖先

3 篇文章 0 订阅
2 篇文章 0 订阅

前言

一个长链剖分的小trick

问题

如题,数据范围大概 1 0 5 10^5 105

思路

我们知道重链剖分是什么,即选择自己儿子中子树节点树最大的作为重儿子,其它儿子为轻儿子
而长链剖分则是选择儿子中子树深度最大的儿子为长儿子,其它为短儿子

这样进行树剖后就有一些很奇妙的性质,即:一个节点的kth祖先所在的链的链长大于k
我们考虑:对于每一条长度为 x x x的链,我们记录链头的每个kth祖先( k ∈ [ 1 , x ] k\in[1,x] k[1,x]),以及链中的一个元素
我们发现,这个的记录时间复杂度和空间复杂度都是 O ( n ) \mathcal O(n) O(n)

那么,现在对于一个询问,求点 u u u k k kth祖先,如果我们能够 O ( 1 ) \mathcal O(1) O(1)的找到其随便的一个 k ′ k' kth祖先( k ′ > ⌊ k 2 ⌋ k'>\left\lfloor\frac k2\right\rfloor k>2k),那我们就可以 O ( 1 ) \mathcal O(1) O(1)的询问 k k kth祖先了(实际操作只需要在 k ′ k' k祖先的数组里进行访问即可)
考虑倍增,我们可以 O ( n l o g n ) \mathcal O(nlogn) O(nlogn)预处理,这样我们就可以直接跳 2 i 2^i 2ith祖先了,在本题中,只要让 i i i满足 2 i ≤ k , 2 i + 1 > k 2^i\le k,2^{i+1}>k 2ik,2i+1>k即可,这个东西我们也可以开个数组 O ( n ) \mathcal O(n) O(n)预处理

综上,这个做法就能做到 O ( n l o g n ) \mathcal O(nlogn) O(nlogn)预处理, O ( 1 ) \mathcal O(1) O(1)询问了

代码

咕咕咕咕咕
好像没有模板题,所以就没了
update by 2019/8/4
碰到了一道需要用这来优化成 O ( n n ) \mathcal O(n\sqrt n) O(nn )
luoguP3591 [POI2015]ODW

#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cmath>
#include<vector>
namespace fast_IO
{
	const int IN_LEN=1000000,OUT_LEN=1000000;
	char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf,*lastin=ibuf+IN_LEN,*lastout=obuf+OUT_LEN-1;
	inline char getchar_(){return (ih==lastin)&&(lastin=(ih=ibuf)+fread(ibuf,1,IN_LEN,stdin),ih==lastin)?EOF:*ih++;}
	inline void putchar_(const char x){if(oh==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;*oh++=x;}
	inline void flush(){fwrite(obuf,1,oh-obuf,stdout);}
}
using namespace fast_IO;
#define getchar() getchar_()
#define putchar(x) putchar_((x))
//#include<ctime>
#define rg register
typedef long long ll;
template <typename T> inline T max(const T a,const T b){return a>b?a:b;}
template <typename T> inline T min(const T a,const T b){return a<b?a:b;}
template <typename T> inline void mind(T&a,const T b){a=a<b?a:b;}
template <typename T> inline void maxd(T&a,const T b){a=a>b?a:b;}
template <typename T> inline T abs(const T a){return a>0?a:-a;}
template <typename T> inline void Swap(T&a,T&b){T c=a;a=b;b=c;}
//template <typename T> inline void swap(T*a,T*b){T c=a;a=b;b=c;}
template <typename T> inline T gcd(const T a,const T b){if(!b)return a;return gcd(b,a%b);}
template <typename T> inline T lcm(const T a,const T b){return a/gcd(a,b)*b;}
template <typename T> inline T square(const T x){return x*x;};
template <typename T> inline void read(T&x)
{
	char cu=getchar();x=0;bool fla=0;
	while(!isdigit(cu)){if(cu=='-')fla=1;cu=getchar();}
	while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
	if(fla)x=-x;
}
template <typename T> inline void printe(const T x)
{
	if(x>=10)printe(x/10);
	putchar(x%10+'0');
}
template <typename T> inline void print(const T x)
{
	if(x<0)putchar('-'),printe(-x);
	else printe(x);
}
const int maxn=50001,maxm=100001;
std::vector<int>kth[maxn];
int head[maxn],nxt[maxm],tow[maxm],tmp;
inline void addb(const int u,const int v)
{
	tmp++;
	nxt[tmp]=head[u];
	head[u]=tmp;
	tow[tmp]=v;
}
int n,part,a[maxn],b[maxn],c[maxn],d[maxn];
int f[maxn][19],dep[maxn],son[maxn],nothead[maxn],F[maxn],top[maxn],las[maxn];
void dfs(const int u,const int fa)
{
	dep[u]=1;
	for(rg int i=head[u];i;i=nxt[i])
	{
		const int v=tow[i];
		if(v==fa)continue;
		f[v][0]=F[v]=u;
		d[v]=d[u]+1,dfs(v,u);
		if(dep[v]+1>dep[u])dep[u]=dep[v]+1,son[u]=v;
	}
}
void dfs2(const int u,const int fa)
{
	las[top[u]]=u;
	for(rg int i=head[u];i;i=nxt[i])
	{
		const int v=tow[i];
		if(v==fa)continue;
		if(v==son[u])top[v]=top[u],dfs2(v,u);
		else top[v]=v,dfs2(v,u);
	}
}
int LCA(int u,int v)
{
	if(d[u]<d[v])Swap(u,v);
	int D=d[u]-d[v];
	for(rg int i=0;i<=16;i++)if(D&(1<<i))u=f[u][i];
	if(u==v)return u;
	for(rg int i=16;i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
	return F[u];
}
int qz[maxn][231],id[maxn];
void DFS(const int u,const int fa)
{
	for(rg int j=1,v=F[u];j<=part;j++)qz[u][j]=qz[v][j]+a[u],v=F[v];
	for(rg int i=head[u];i;i=nxt[i])
	{
		const int v=tow[i];
		if(v==fa)continue;
		DFS(v,u);
	}
}
int ks[65537];
int fe(int u,int dis)
{
	if(dis==0)return u;
	u=f[u][ks[dis]];
	dis-=1<<ks[dis];
	if(u==0)return 0;
	return kth[top[u]][id[u]+dis];
}
int main()
{
	d[1]=1;
	for(rg int i=1;i<=16;i++)
		for(rg int j=1<<(i-1);j<(1<<i);j++)
			ks[j]=i-1;
	read(n),part=20;
	for(rg int i=1;i<=n;i++)read(a[i]);
	for(rg int i=1;i<n;i++)
	{
		int u,v;read(u),read(v);
		addb(u,v),addb(v,u);
	}
	for(rg int i=1;i<=n;i++)read(b[i]);
	for(rg int i=1;i<n;i++)read(c[i]);
	dfs(1,0);
	top[1]=1,dfs2(1,0);
	for(rg int i=1;i<=16;i++)
		for(rg int j=1;j<=n;j++)
			f[j][i]=f[f[j][i-1]][i-1];
	for(rg int i=1;i<=n;i++)nothead[son[i]]=1;
	for(rg int i=1;i<=n;i++)
		if(!nothead[i])
		{
			std::vector<int>&t=kth[i];
			const int S=dep[i]<<1;
			t.resize(S+1);
			for(rg int j=1,u=las[i];u&&j<=S;j++)
			{
				if(j<=dep[i])id[u]=j;
				t[j]=u,u=F[u];
			}
		}
	DFS(1,0);
	for(rg int i=1;i<n;i++)
	{
		int u=b[i],v=b[i+1],step=c[i],lca=LCA(u,v);
		if(step<=part)
		{
			int du=d[u]-d[lca],dv=d[v]-d[lca];
			int ans=0;
			int extra=du%step;du=du-extra+step;
			ans+=qz[u][step]-qz[fe(u,du)][step];
			dv+=extra;
			if(dv==0)print(ans);
			else
			{
				ans+=a[v];
				extra=(dv-1)%step+1;
				ans+=qz[fe(v,extra)][step]-qz[fe(v,dv)][step];
				print(ans);
			}
		}
		else
		{
			int ans=0;
			while(d[u]>=d[lca])
			{
				ans+=a[u];
				u=fe(u,step);
			}
			while(d[v]>d[lca])
			{
				ans+=a[v];
				v=fe(v,step);
			}
			print(ans);
		}
		putchar('\n');
	}
	return flush(),0; 
}

总结

挺巧妙的一个思路,在求kth祖先的次数比较多的时候可以使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值