loj 6184 无心行挽 虚树+DP+倍增

题目分析

首先,这题肯定要用虚树。

接下来,你会发现,使得 f ( u ) f(u) f(u)最大的 u u u,可能存在于三个位置:

  1. 虚树上
  2. 虚树上一个没有关键点的子树中
  3. 虚树上的一条边中

于是便分类讨论。

对于第一种情况,只需要在虚树上做DP求出虚树上的每个点 x x x到最近的关键点的距离 d t k p ( x ) dtkp(x) dtkp(x)即可(dist to key point)

对于第二种情况,我们对整棵树DP,得到每个点到子树中的点的最远距离 f i r ( x ) fir(x) fir(x),然后将它的儿子们按照fir从大到小排序。对于虚树上的每个点 x x x,找到第一个不在虚树上的儿子 y y y,用 f i r ( y ) + 1 + d t k p ( x ) fir(y)+1+dtkp(x) fir(y)+1+dtkp(x)更新答案。

对于第三种情况,比较麻烦。我们对整棵树DP的时候,还要得到一个 s e ( x ) se(x) se(x),表示 x x x不走能够造成 f i r ( x ) fir(x) fir(x)贡献的那棵子树,离最远点的距离。于是我们就可以预处理两个倍增数组: U ( j , i ) U(j,i) U(j,i) D ( j , i ) D(j,i) D(j,i) j = 0 j=0 j=0时的值。

U ( j , i ) U(j,i) U(j,i) i i i走到原树上的前 2 j 2^j 2j个祖先中的一个后,再往该祖先的一棵子树里走,用这样的策略可以走的最远距离。

D ( j , i ) D(j,i) D(j,i) i i i的原树上前 2 j 2^j 2j个祖先中的一个,往自己的子树里走,不经过子树 i i i可以走的最远距离。

不难发现,如果我们规定虚树上的一个点 x x x和它儿子 y y y中间边上的点,全部必须先走到 x x x再走到关键点,贡献可以通过 D D D算出来。而若全部必须先走到 y y y再走到关键点,贡献可以通过 U U U算出来。

通过 d t k p ( x ) dtkp(x) dtkp(x) d t k p ( y ) dtkp(y) dtkp(y)的值,我们就可以确定这条边从哪个位置开始划分,上面的点全走到 x x x再去关键点,下面的点全走到 y y y再去关键点,就能算出答案了。

复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
const int N=100005,inf=0x3f3f3f3f;
int n,Q,tot,ans,qcnt,tim,top,rub_top;
int h[N],ne[N<<1],to[N<<1],pos[N],fir[N],se[N];
int f[17][N],U[17][N],D[17][N],dep[N],bin[17];
int q[N],is[N],st[N],dtkp[N],rub[N],cantGo[N];
vector<int> son[N];

void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
bool cmpson(int x,int y) {return fir[x]>fir[y];}
void dfs(int x,int las) {
	f[0][x]=las,dep[x]=dep[las]+1,pos[x]=++tim;
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		dfs(to[i],x),son[x].push_back(to[i]);
		if(fir[to[i]]+1>fir[x]) se[x]=fir[x],fir[x]=fir[to[i]]+1;
		else if(fir[to[i]]+1>se[x]) se[x]=fir[to[i]]+1;
	}
	sort(son[x].begin(),son[x].end(),cmpson);
	for(RI i=h[x];i;i=ne[i]) {
		if(to[i]==las) continue;
		if(fir[to[i]]+1==fir[x]) U[0][to[i]]=se[x]+1,D[0][to[i]]=se[x];
		else U[0][to[i]]=fir[x]+1,D[0][to[i]]=fir[x];
	}
}
void prework() {
	bin[0]=1;for(RI i=1;i<=16;++i) bin[i]=bin[i-1]<<1;
	for(RI j=1;j<=16;++j)
		for(RI i=1;i<=n;++i) {
			f[j][i]=f[j-1][f[j-1][i]];
			U[j][i]=max(U[j-1][i],U[j-1][f[j-1][i]]+bin[j-1]);
			D[j][i]=max(D[j-1][i]+bin[j-1],D[j-1][f[j-1][i]]);
		}
}
int lca(int x,int y) {
	if(dep[x]<dep[y]) swap(x,y);
	for(RI i=16;i>=0;--i) if(dep[f[i][x]]>=dep[y]) x=f[i][x];
	if(x==y) return x;
	for(RI i=16;i>=0;--i) if(f[i][x]!=f[i][y]) x=f[i][x],y=f[i][y];
	return f[0][x];
}

int goup(int x,int d) {
	for(RI i=16;i>=0;--i) if(d&bin[i]) x=f[i][x];
	return x;
}
int getD(int x,int d) {
	int re=0;
	for(RI i=16;i>=0;--i)
		if(d&bin[i]) d^=bin[i],re=max(re,d+D[i][x]),x=f[i][x];
	return re;
}
int getU(int x,int d) {
	int re=0,kdist=0;
	for(RI i=16;i>=0;--i)
		if(d&bin[i]) re=max(re,kdist+U[i][x]),kdist|=bin[i],x=f[i][x];
	return re;
}
bool cmp(int x,int y) {return pos[x]<pos[y];}
void DP1(int x) {
	if(is[x]) dtkp[x]=0; else dtkp[x]=inf;
	for(RI i=h[x];i;i=ne[i]) {
		DP1(to[i]);
		dtkp[x]=min(dtkp[x],dtkp[to[i]]+dep[to[i]]-dep[x]);
		int k=goup(to[i],dep[to[i]]-dep[x]-1);
		cantGo[k]=1,rub[++rub_top]=k;
	}
}
void DP2(int x) {
	for(RI i=h[x];i;i=ne[i]) {
		dtkp[to[i]]=min(dtkp[to[i]],dtkp[x]+dep[to[i]]-dep[x]);
		DP2(to[i]);
	}
}
void getans(int x) {
	ans=max(ans,dtkp[x]);//虚树上
	for(RI i=h[x];i;i=ne[i]) {
		int d=dep[to[i]]-dep[x];
		if(d>1) {//虚树边上
			if(dtkp[to[i]]-dtkp[x]==d)
				ans=max(ans,getD(to[i],d-1)+1+dtkp[x]);
			else if(dtkp[x]-dtkp[to[i]]==d)
				ans=max(ans,getU(to[i],d-1)+dtkp[to[i]]);
			else {
				int kd=(dep[to[i]]-dep[x]+dtkp[x]-dtkp[to[i]])/2;
				int k=goup(to[i],kd);
				if(f[0][k]!=x) ans=max(ans,getD(k,dep[k]-dep[x]-1)+1+dtkp[x]);
				ans=max(ans,getU(to[i],kd)+dtkp[to[i]]);
			}
		}
		getans(to[i]);
	}
	int sz=son[x].size();//无关键点的子树中
	for(RI i=0;i<sz;++i)
		if(!cantGo[son[x][i]]) {ans=max(ans,fir[son[x][i]]+1+dtkp[x]);break;}
	h[x]=0;
}
void work() {
	ans=top=tot=rub_top=0;
	sort(q+1,q+1+qcnt,cmp);
	if(!is[1]) st[++top]=1;
	for(RI i=1;i<=qcnt;++i) {
		int o=0,x=q[i];
		while(top) {
			o=lca(x,st[top]);
			if(top>1&&dep[st[top-1]]>dep[o]) add(st[top-1],st[top]),--top;
			else if(dep[st[top]]>dep[o]) {add(o,st[top]),--top;break;}
			else break;
		}
		if(st[top]!=o) st[++top]=o;
		st[++top]=x;
	}
	while(top>1) add(st[top-1],st[top]),--top;
	DP1(1),DP2(1),getans(1);
	for(RI i=1;i<=qcnt;++i) is[q[i]]=0;
	for(RI i=1;i<=rub_top;++i) cantGo[rub[i]]=0;
}

int main()
{
	int x,y;
	n=read(),Q=read();
	for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
	dfs(1,0),prework();
	tot=0;for(RI i=1;i<=n;++i) h[i]=0;
	while(Q--) {
		qcnt=read();
		for(RI i=1;i<=qcnt;++i) q[i]=read(),is[q[i]]=1;
		work(),printf("%d\n",ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值