【THUPC2019】不用找的树 / tree(树分块)(树形DP)

传送门


是我没见过的树分块姿势,出题人写的题解跟****一样,只有会这道题的人才看得懂,反正我没看懂,我看了std那8K难以形容的代码才知道题解真就NM乱写。

严格来说,只有分块的具体方法那里是乱写,但是那里乱写直接导致了后续的无法理解。

出题人:反正预计也不会有人想去写,题解乱写就行了。

那乱写说是垃圾也就不为过了。

所以这里来一个我的视角的题解。

更离谱的是树上距离的定义居然是路径上点数而不是边数,答案相差的值需要统计距离一个点不超过 d d d 的点的数量才能算出来,又要写个数据结构,所以好点的办法就是直接算出来答案,于是一大堆细节和一般写的树上处理都不一样,真的就TM离谱。

题解:

首先考虑一个暴力,只有一个询问,怎么 O ( n ) O(n) O(n) 处理出答案。

容易注意到我们只需要求出树上每个点作为了多少个路径的LCA即可,一次dfs算出子树中有多少个 A A A 集合的点,然后一次dfs在每个 B B B 集合的点处统计答案。

这个暴力复杂度只和树上的点数相关。

考虑树分块,这里题解也是在TM瞎说,出题人****。

我们希望分块后各个块的相邻关系也是树形结构,考虑如下的分块策略:

块大小往 O ( n ) O(\sqrt n) O(n ) 靠,这是常识。

一个点可能存在于多个块中,每个块最多允许两个节点存在于其他块中(题解里面根本没有描述这个细节,直接相邻两个字就带过了,NM重合真就当相邻了?)。每个块一定有一个节点深度最高,称为 top,另一个节点我们要求其在块中没有儿子,且它可能存在于另外的块中(都是作为top),这个节点称为bot,注意一个块如果是叶子块,我们允许其没有bot。

这样块与块之间的相邻关系仍然是树形。

定义邻域域表示在某个块中距离某个点不超过 d d d 的节点集合。

那么一个领域可以拆到各个快里面,且除了中心所在的块,其他块中的邻域中心都是top或bot。

考虑将询问表示成这几种询问的结果之和。

  1. 不同块中的邻域的距离之和。
  2. 同一块内,且两个中心处于两个端点(可能处于同一个端点)的领域的距离之和。
  3. 同一块内,一个或两个中心不处于端点的邻域的距离之和。

首先考虑第三种询问,每个大询问只会导致 O ( 1 ) O(1) O(1) 个这种小询问,在块内 O ( n ) O(\sqrt n) O(n ) DP一下即可。

考虑第二种询问,由于一个块只有两个端点,那么一个块需要处理的本质不同的询问只有 Θ ( m x d e p 2 ) ≤ O ( n 2 ) = O ( n ) \Theta(mxdep^2)\leq O(\sqrt n^2)=O(n) Θ(mxdep2)O(n 2)=O(n),对于 O ( n ) O(\sqrt n) O(n ) 个块,可以每个暴力处理出来答案,我写的是最后离线每个块搞一搞。

然后是第一种询问,由于块之间的相邻关系是树形,树形DP一下即可。

不建议写,但如果已经下定决心要写也不要放弃,大力写总是有奇迹的(虽然这个奇迹花了三天)。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
#define int ll

namespace IO{

inline char gc(){
	static cs int Rlen=1<<22|1;static char buf[Rlen],*p1,*p2;
	return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
}template<typename T>T get_integer(){
	char c;bool f=false;while(!isdigit(c=gc()))f=c=='-';T x=c^48;
	while(isdigit(c=gc()))x=((x+(x<<2))<<1)+(c^48);return f?-x:x;
}inline int gi(){return get_integer<int>();}

char obuf[(int)(3e7+7)],*oh=obuf,ch[23];
template<typename T>void print(T a,char c){
	if(a<0)*oh++='-',a=-a;int tl=0;
	do ch[++tl]=a%10;while(a/=10);
	while(tl)*oh++=ch[tl--]^48;*oh++=c;
}struct obuf_flusher{~obuf_flusher(){fwrite(obuf,1,oh-obuf,stdout);}}Flusher;

}using namespace IO;

using std::cerr;

cs int N=1e5+7,BN=3e3+7;

int n,m,B;ll ans[N];
std::vector<int> G[N];
int fa[N],bt[N],sz[N];
int tr[N],bel[N],ict;
int q[N],ql,qr,bct;

struct Qry{int d0,d1,d,id;};
struct Block{
	int idl,sz,top,bot,*fa,*ch;
	int *d[2],mxd[2],*dct[2],*dsm[2][2];
	std::vector<Qry> qry;
	inline int id(int x){return tr[x]-idl;}
	void build(int tp,int bt,int bid){
		top=tp,bot=bt;q[0]=tp;
		tr[tp]=idl=ict;
		while(ql!=qr){
			int u=q[++ql];
			bel[tr[u]=++ict]=bid;
			if(::sz[u]>1)
				for(int v:G[u])
					q[++qr]=v;
		}sz=qr+1;
		fa=new int[sz];ch=new int[sz+1];
		memset(fa,0,sizeof(int)*sz);
		memset(ch,0,sizeof(int)*(sz+1));
		int p=0;for(int re i=1;i<sz;++i){
			fa[i]=tr[::fa[q[i]]]-idl;
			while(p<fa[i])ch[++p]=i;
		}while(p<sz)ch[++p]=sz;
		d[0]=new int[sz];d[0][0]=0;
		for(int re i=1;i<sz;++i)
			d[0][i]=d[0][fa[i]]+1;
		mxd[0]=d[0][sz-1]+1;
		cal_dc(0);cal_dsm(0,0);
		if(bt){
			d[1]=new int[sz];
			memset(d[1],0,sizeof(int)*sz);
			ql=0;d[1][q[qr=1]=id(bt)]=1;
			while(ql<qr){
				int u=q[++ql],D=(u==id(bt)?0:d[1][u])+1;
				if(!d[1][fa[u]])d[1][q[++qr]=fa[u]]=D;
				for(int re i=ch[u];i<ch[u+1];++i)
					if(!d[1][i])d[1][q[++qr]=i]=D;
			}d[1][id(bt)]=0;mxd[1]=d[1][q[qr]]+1;
			cal_dc(1);cal_dsm(0,1),cal_dsm(1,0),cal_dsm(1,1);
		} 
	}void cal_dc(int t){
		int md=mxd[t],*dc=dct[t]=new int[md];
		memset(dc,0,sizeof(int)*md);
		for(int re i=1;i<sz;++i)++dc[d[t][i]];
		for(int re i=1;i<md;++i)dc[i]+=dc[i-1];
	}void cal_dsm(int k,int l){
		int md=mxd[k],*d0=d[k],*d1=d[l];
		int *ds=dsm[k][l]=new int[md];
		memset(ds,0,sizeof(int)*md);
		for(int re i=1;i<sz;++i)ds[d0[i]]+=d1[i];
		for(int re i=1;i<md;++i)ds[i]+=ds[i-1];
	}void get(int u,int dl,int *ed,int &ds0,int &ds1,int &dc)cs{
		memset(ed,0,sizeof(int)*sz);
		if(dl>mxd[0]+d[0][u]){
			ds0=dsm[0][0][mxd[0]-1];
			if(bot)ds1=dsm[0][1][mxd[0]-1];
			dc=sz-1;
			for(int re i=sz-1;i;--i)
				ed[fa[i]]+=++ed[i];
			return ;
		}ql=0;ed[q[qr=1]=u]=1;
		for(;ql<qr&&dl>0;--dl){
			for(int re r=qr;ql<r;){
				int u=q[++ql];
				for(int re i=ch[u];i<ch[u+1];++i)
					if(!ed[i])ed[q[++qr]=i]=1;
				if(!ed[fa[u]])ed[q[++qr]=fa[u]]=1; 
			}
		}
		dc=qr-ed[0];
		for(int re i=1;i<sz;++i)
			if(ed[i]){
				ds0+=d[0][i];
				if(bot)ds1+=d[1][i];
			}
		for(int re i=sz-1;i;--i)
			ed[fa[i]]+=ed[i];
	}ll calc(int *a,int *b)cs{
		ll r=0;
		for(int re i=1;i<sz;++i)
			r+=(ll)a[i]*b[i];
		return r;
	}void solve(int,int)cs;
}bl[BN];

void build_block(int tp,int bt){
	if(ql==qr)return ;
	q[0]=tp;++bct;
	bl[bct].build(tp,bt,bct);
	ql=qr=0;
}

void build_dfs(int u){
	int s=0,d=0;
	for(int v:G[u]){
		build_dfs(v);s+=sz[v];
		if(bt[v])++d,bt[u]=bt[v];
	}
	if(s>=B||d>=2||u==0){
		bt[u]=u;s=0,d=0;
		std::sort(G[u].begin(),G[u].end(),
			[](int u,int v){return sz[u]<sz[v];});
		for(int v:G[u]){
			if(s>=B||(d&&bt[v])){
				build_block(u,d);
				s=0,d=0;
			}if(!d)d=bt[v];
			q[++qr]=v,s+=sz[v];
		}build_block(u,d),sz[u]=1;
	}else sz[u]=1+s;
}

namespace BlockTree{

int fa[BN],ch[BN],cr[BN],len[BN];

void build(){
	build_dfs(0);
	for(int re i=1;i<=bct;++i){
		auto &t=bl[i];
		if(t.bot)len[i]=t.d[0][t.id(t.bot)];
		for(int re j=i+1;j<=bct;++j)
			if(bl[j].bot==t.top)
				{++cr[fa[i]=j];break;}
	}
	for(int re i=1;i<=bct+1;++i)cr[i]+=cr[i-1];
	for(int re i=1;i<bct;++i)ch[--cr[fa[i]]]=i;
}
struct node{
	ll ds;int sz;node(){}node(ll a,int b):ds(a),sz(b){}
	void inc(cs node &a,int d){ds+=a.ds+(ll)a.sz*d,sz+=a.sz;}
	node operator-(cs node &rhs)cs{return node(ds-rhs.ds,sz-rhs.sz);}
	ll operator^(cs node &rhs)cs{return ds*rhs.sz+rhs.ds*sz;}
};
struct Sol{
	int ct[BN],ctd[BN];
	node ds0[BN],ds1[BN];
	int b,b1,Ds0,Ds1,dc;
	void init(int p,int d,int _b1,int qid){
		b1=_b1;b=bel[p];
		memset(ds0,0,sizeof(node)*(bct+2));
		memset(ds1,0,sizeof(node)*(bct+2));
		memset(ctd,0,sizeof(int)*bl[b1].sz);
		auto &T=bl[b];Ds0=Ds1=dc=0;p-=T.idl;
		T.get(p,d,ct,Ds0,Ds1,dc);
		ds0[b]={Ds0,dc};if(T.bot)ds1[b]={Ds1,dc};
		T.qry.push_back({T.d[0][p],(T.bot?T.d[1][p]:0),d,qid});
		if(fa[b]){
			int f=fa[b],d1=d-T.d[0][p];
			dfs(f,d1,1);
			for(int re i=cr[f];i<cr[f+1];++i)
				if(ch[i]!=b)dfs(ch[i],d1,0);
		}if(T.bot){
			int d1=d-T.d[1][p];
			for(int re i=cr[b];i<cr[b+1];++i)
				dfs(ch[i],d1,0);
		}
	}
	void dfs(int u,int d,int tp){
		if(d<0)return ;auto &T=bl[u];
		int d1=std::min(T.mxd[tp]-1,d),dc=T.dct[tp][d1];
		ds0[u]={T.dsm[tp][0][d1],dc};
		if(T.bot)ds1[u]={T.dsm[tp][1][d1],dc};
		if(u==b1){
			for(int re i=1;i<T.sz;++i)
				ctd[i]=(T.d[tp][i]<=d);
			for(int re i=T.sz-1;i;--i)
				ctd[T.fa[i]]+=ctd[i];
		}d-=len[u];
		if(tp){
			int f=fa[u];if(!f)return;dfs(f,d,1);
			for(int re i=cr[f];i<cr[f+1];++i)
				if(ch[i]!=u)dfs(ch[i],d,0);
		}else for(int re i=cr[u];i<cr[u+1];++i)
			dfs(ch[i],d,0);
	}
	void calc_all(){
		for(int re u=1;u<bct;++u)
			ds0[fa[u]].inc(ds0[u],len[fa[u]]);
		for(int re u=bct;u;--u){
			for(int re i=cr[u];i<cr[u+1];++i)
				ds1[u].inc(ds0[ch[i]],0);
			for(int re i=cr[u];i<cr[u+1];++i)
				ds1[ch[i]].inc(ds1[u]-ds0[ch[i]],len[ch[i]]);
		}
	}
}t0,t1;
void Query(int p0,int d0,int p1,int d1,int qid){
	t0.init(p0,d0,bel[p1],qid);
	t1.init(p1,d1,bel[p0],qid);
	ll s=0;int b0=bel[p0],b1=bel[p1];
	auto &B0=bl[b0],&B1=bl[b1];
	if(b0==b1){
		s+=(t0.ds0[b0]^t1.ds0[b1])-2*B0.calc(t0.ct,t1.ct);
	}else {
		s+=(t0.ds0[b0]^t1.ds0[b0])-2*B0.calc(t0.ct,t1.ctd);
		s+=(t0.ds0[b1]^t1.ds0[b1])-2*B1.calc(t1.ct,t0.ctd);
	}
	t0.calc_all();
	for(int re u=1;u<=bct;++u){
		for(int re i=cr[u];i<cr[u+1];++i){
			int v=ch[i];
			ans[qid]+=(t0.ds1[u]-t0.ds0[v])^t1.ds0[v];
			ans[qid]+=t0.ds0[v]^t1.ds1[u];
		}
	}ans[qid]+=s;
}
int cur_d,qs[N][2],qp[N];
void dfs(int u,int d,int tp){
	for(auto &q:bl[u].qry){
		int d1=q.d-(tp?q.d1:q.d0)-d;
		if(d1>=0)
			qs[q.id][qp[q.id]++]=d1*2+cur_d;
	}d+=len[u];
	if(tp){
		int f=fa[u];if(f)dfs(f,d,1);
		for(int re i=cr[f];i<cr[f+1];++i)
			if(ch[i]!=u)dfs(ch[i],d,0);
	}else for(int re i=cr[u];i<cr[u+1];++i)
		dfs(ch[i],d,0);
}
void solve(){
	for(int re u=1;u<=bct;++u){
		memset(qs,0,sizeof(int)*(m+2)*2);
		memset(qp,0,sizeof(int)*(m+2));
		if(fa[u]){
			int p=fa[u];cur_d=0;dfs(p,0,1);
			for(int re i=cr[p];i<cr[p+1];++i)
				if(ch[i]!=u)dfs(ch[i],0,0);
		}cur_d=1;
		for(int re i=cr[u];i<cr[u+1];++i)
			dfs(ch[i],0,0);
		auto &T=bl[u];
		T.solve(0,0);
		if(T.bot){
			T.solve(0,1);
			T.solve(1,1);
		}
	}
}

}

void Block::solve(int t0,int t1)cs{
	using BlockTree::qs;
	using BlockTree::qp;
	int md0=mxd[t0],md1=mxd[t1];
	int *d0=d[t0],*d1=d[t1];
	static ll tmp[BN][BN],vl[BN];
	for(int re a=0;a<md0;++a){
		memset(vl,0,sizeof(ll)*sz);
		for(int re i=1;i<sz;++i)
			vl[i]=(d0[i]<=a);
		for(int re i=sz-1;i;--i)
			vl[fa[i]]+=vl[i];
		vl[0]=0;
		for(int re i=1;i<sz;++i)
			vl[i]+=vl[fa[i]];
		memset(tmp[a],0,sizeof(ll)*md1);
		for(int re i=1;i<sz;++i)
			tmp[a][d1[i]]+=vl[i];
		for(int re i=1;i<sz;++i)
			tmp[a][i]+=tmp[a][i-1];
	}
	for(int re i=1;i<=m;++i)if(qp[i]==2){
		int a=qs[i][0],b=qs[i][1];
		if(!(((a^t0)|(b^t1))&1)){
			a=std::min(md0-1,a>>1);
			b=std::min(md1-1,b>>1);
			ans[i]+=
				dct[t0][a]*dsm[t1][0][b]+
				dct[t1][b]*dsm[t0][0][a]-
				2*tmp[a][b];
		}
	}
}

void Main(){
	n=gi();B=sqrt(n)*2;
	G[0].push_back(1);
	for(int i=2;i<=n;++i)
		G[fa[i]=gi()].push_back(i);
	BlockTree::build();m=gi();
	for(int re i=1;i<=m;++i){
		int p0=gi(),d0=gi(),p1=gi(),d1=gi();
		BlockTree::Query(tr[p0],d0,tr[p1],d1,i);
	}BlockTree::solve();
	for(int re i=1;i<=m;++i)
		print(ans[i],'\n');
}

inline void file(){
#ifdef zxyoi
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
#endif
}signed main(){file();Main();return 0;} 
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值