bzoj3611: [Heoi2014]大工程

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3611

思路:构建虚树DP

首先这种题有一个特征,就是所有询问的总点数是O(n)的

那么就可以考虑对每次询问建一棵虚树,再在虚树上DP。

那么我们对于每次询问,就不一定要把整棵树建出来,而是只要管一部分点即可

比如u,v两点之间没有其他询问点,那么我们就可以把uv直接连起来,中间的点是什么我们并不关心。

我们只要建出这样一棵树即可:只含当前询问点和它们的lca,并且相对位置关系不变

举个例子:原树是这样的,假如选了1、5、8、10号节点


构建出来的虚树就是:

怎样构建虚树?

先对原图做一边dfs预处理,得出dfn和一些乱七八糟的东西。

对于每次询问,先把所有询问点按dfn排序,一个一个插入。

我们要用栈维护一个叫“最右链”的东西,就是最右边的一条链...

每次和最右链的最深的一个点求lca,并动态维护之。

如果栈为空,直接加进栈,

否则求出栈顶元素stk[top]和当前点的lca

如果dfn[lca]<dfn[stk[top]],那就一直弹栈直到dfn[lca]>=dfn[stk[top]],因为这些点都不会在新的最右链里。

(记得特判lca是否已在栈中)

看图直观理解就是左边的点与当前点求lca不会贡献出新的点,所以只用管最右链即可



这样我们每次就可以O(m)的构建虚树了,这也是为什么这些题都有Σq[i]<=O(n)的限制。

用虚树还要满足一个条件,就是要维护的信息

例如,和,最大,最小,都有类似于前缀和的性质,

就是我们可以从v(u的后代)直接求出u的答案,而不需要遍历u到v的所有边

否则虚树就没有降低复杂度,因为每次还是要在原树上走。


对于这题,剩下的只有在虚树上treeDP了。

记录f[i]表示在i的子树中的路径总长,siz[i]表示i的子树中询问点的个数

maxs[i]表示i子树中询问点到根的最长路长,mins[i]同理。

转移很显然 f[i]=f[son[i]]+siz[son[y]]*(cnt-siz[son[y]])*dis(i,son[i])   (cnt是此次询问总点数)

对于maxs[i]

如果i是询问点,那就只要在子树中找另一个询问点即可

即我们可以直接用maxs[i]更新答案。

否则我们要找两个点,用maxs[i]+maxs[son[i]]+dis(i,son[i])更新答案

最小同理


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
typedef long long ll;
const int maxn=1000010,maxm=maxn<<1,maxk=23,inf=1e9;
using namespace std;
int n,q,dis[maxn],dfn[maxn],tim,dep[maxn],fa[maxn][maxk],num,poi[maxn];
int stk[maxn],top,siz[maxn],maxs[maxn],mins[maxn],ans1,ans2;
ll f[maxn];bool bo[maxn];
bool cmp(int a,int b){return dfn[a]<dfn[b];}
struct Tgraph{
	int pre[maxm],now[maxn],son[maxm],tot;
	void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}
	void dfs1(int x){
		dfn[x]=++tim;
		for (int i=1;i<=20;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
		for (int y=now[x];y;y=pre[y]) if (son[y]!=fa[x][0])
			dis[son[y]]=dis[x]+1,dep[son[y]]=dep[x]+1,fa[son[y]][0]=x,dfs1(son[y]);
	}
	void dfs2(int x){
		siz[x]=bo[x],maxs[x]=0,mins[x]=inf,f[x]=0;
		for (int y=now[x];y;y=pre[y]){
			int d=dis[son[y]]-dis[x];
			dfs2(son[y]),siz[x]+=siz[son[y]];
			ans1=min(ans1,mins[x]+mins[son[y]]+d),mins[x]=min(mins[x],mins[son[y]]+d);
			ans2=max(ans2,maxs[x]+maxs[son[y]]+d),maxs[x]=max(maxs[x],maxs[son[y]]+d);
			f[x]+=f[son[y]]+1ll*siz[son[y]]*(num-siz[son[y]])*d;
		}
		if (bo[x]) ans1=min(ans1,mins[x]),ans2=max(ans2,maxs[x]),mins[x]=0;
		now[x]=0;
	}
}g1,g2;

int lca(int a,int b){
	if (dep[a]<dep[b]) swap(a,b);
	for (int h=dep[a]-dep[b],i=20;i>=0;i--) if (h>=(1<<i)) h-=(1<<i),a=fa[a][i];
	if (a==b) return a;
	for (int i=20;i>=0;i--) if (fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i];
	return fa[a][0];
}

void work(){
	top=0;
	for (int i=1;i<=num;i++){
		if (!top){stk[++top]=poi[i];continue;}
		int u=lca(stk[top],poi[i]);
		while (dfn[u]<dfn[stk[top]]){
			if (dfn[u]>=dfn[stk[top-1]]){
				g2.add(u,stk[top]);
				if (stk[--top]!=u) stk[++top]=u;
				break;
			}
			g2.add(stk[top-1],stk[top]),top--;
		}
		stk[++top]=poi[i];
	}
	while (top>1) g2.add(stk[top-1],stk[top]),top--;
	ans1=inf,ans2=0,g2.dfs2(stk[1]);
	printf("%lld %d %d
",f[stk[1]],ans1,ans2);
	for (int i=1;i<=num;i++) bo[poi[i]]=0;g2.tot=0;
}

int main(){
	scanf("%d",&n);
	for (int i=1,a,b;i<n;i++) scanf("%d%d",&a,&b),g1.add(a,b),g1.add(b,a);
	g1.dfs1(1),scanf("%d",&q);
	for (int i=1;i<=q;i++){
		scanf("%d",&num);
		for (int j=1;j<=num;j++) scanf("%d",&poi[j]),bo[poi[j]]=1;
		sort(poi+1,poi+1+num,cmp),work();
	}
	return 0;
}


原文地址:https://www.cnblogs.com/thythy/p/5493493.html