bzoj3572: [Hnoi2014]世界树

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

思路:注意到m[1]+m[2]+…+m[q]<=300000

上虚树Dp。

先DP出虚树上每个点被哪个点管辖,记为bel[i]。

这个从上到下更新一次答案,在从下到上更新一次答案即可。

对于最终答案,我们遍历一遍虚树,把虚树每条边对应的点划分好即可


然后我们考虑虚树的一条边(a,b)

记x为既是a的儿子又是b的祖先的点,siz[a]表示a点的子树大小

1.如果bel[a]==bel[b]那么这条边所对应的一堆实际的点也应该归bel[a]管辖

那么f[bel[a]]+=siz[x]-siz[b]

2.如果bel[a]!=bel[b]那么这条边(在原树上是一条路径)中一定有一个分界点mid

使得mid及以下的点归bel[b]管辖,mid以上的点归bel[a]管辖

倍增找出mid即可

那么f[bel[a]]+=siz[x]-siz[mid],f[bel[b]]+=siz[mid]-siz[b]


最后要处理的是完全没有在虚树上出现的子树,这些子树肯定被控制它们的根的点所控制

我们记录一个rem[i]表示i的子树现在还有多少个点没被统计答案,初值为siz[i]

处理了一条边(a,b)后,那么x的子树就在处理虚树时已经被我们处理完了,记得减去,rem[a]-=siz[x]

最后把这些多余的点加回去即可,f[bel[a]]+=rem[a]

然后有一个细节,记得把虚树的根的rem设为整个树的大小,而不是它的siz,因为那样会使我们漏掉原来的根的其他子树


实现的时候注意细节(各种乱七八糟的问题)....

不知道为什么这么慢....(看来是写丑了)


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
const int maxn=300010,maxm=600010,maxk=22;
using namespace std;
int n,m,fa[maxn][maxk],dep[maxn],siz[maxn],dfn[maxn],tim,f[maxn],cnt,poi[maxn],bel[maxn],stk[maxn],top,ordc,ord[maxn],seq[maxn],rem[maxn];
bool bo[maxn];char ch;
bool cmp(int a,int b){return dfn[a]<dfn[b];}
void read(int &x){
	for (ch=getchar();!isdigit(ch);ch=getchar());
	for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
}
int lca(int a,int b){
	if (dep[a]<dep[b]) swap(a,b);
	for (int h=dep[a]-dep[b],i=19;i>=0;i--) if (h>=(1<<i)) h-=(1<<i),a=fa[a][i];
	if (a==b) return a;
	for (int i=19;i>=0;i--) if (fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i];
	return fa[a][0];
}
int getd(int a,int b){return dep[a]+dep[b]-(dep[lca(a,b)]<<1);}

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){
		siz[x]=1,dfn[x]=++tim;
		for (int i=1;i<=19;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])
			dep[son[y]]=dep[x]+1,fa[son[y]][0]=x,dfs1(son[y]),siz[x]+=siz[son[y]];
	}
	void dfs2(int x){//找子树中最近的关键点
		ord[++ordc]=x,rem[x]=siz[x];
		for (int y=now[x];y;y=pre[y]){
			int v=son[y];dfs2(v);
			if (!bel[v]) continue;
			int t1=getd(x,bel[v]),t2=getd(x,bel[x]);
			if (!bel[x]||t1<t2||(t1==t2&&bel[v]<bel[x])) bel[x]=bel[v];
		}
	}
	void dfs3(int x){//找上面最近的关键点
		for (int y=now[x];y;y=pre[y]){
			int v=son[y],t1=getd(v,bel[x]),t2=getd(bel[v],v);
			if (!bel[v]||t1<t2||(t1==t2&&bel[x]<bel[v])) bel[v]=bel[x];
			dfs3(v);
		}
	}
	void solve(int a,int b){//处理虚边a->b
		int x=b,mid=b;
		for (int i=19;i>=0;i--) if (dep[fa[x][i]]>dep[a]) x=fa[x][i];
		rem[a]-=siz[x];
		if (bel[a]==bel[b]){f[bel[a]]+=siz[x]-siz[b];return;}
		for (int i=19;i>=0;i--){
			int nxt=fa[mid][i];
			if (dep[nxt]<=dep[a]) continue;
			int t1=getd(bel[a],nxt),t2=getd(bel[b],nxt);
			if (t2<t1||(t1==t2&&bel[b]<bel[a])) mid=nxt;
		}
		f[bel[a]]+=siz[x]-siz[mid],f[bel[b]]+=siz[mid]-siz[b];
	}
	void getans(){
		for (int i=1;i<=ordc;i++) for (int y=now[ord[i]];y;y=pre[y]) solve(ord[i],son[y]);
		for (int i=1;i<=ordc;i++) f[bel[ord[i]]]+=rem[ord[i]];
		for (int i=1;i<=cnt;i++) printf("%d ",f[seq[i]]);puts("");
	}
}g1,g2;

void work(){
	stk[top=1]=poi[1];
	for (int i=2;i<=cnt;i++){
		int u=lca(poi[i],stk[top]);
		while (dfn[stk[top]]>dfn[u]){
			if (dfn[stk[top-1]]<=dfn[u]){
				g2.add(u,stk[top]);
				if (u!=stk[--top]) 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--;
	g2.dfs2(stk[1]),g2.dfs3(stk[1]),rem[stk[1]]=siz[1],g2.getans();
	for (int i=1;i<=ordc;i++){int p=ord[i];f[p]=g2.now[p]=rem[p]=bel[p]=0;}
	g2.tot=ordc=0;
}

int main(){
	read(n);
	for (int i=1,a,b;i<n;i++) read(a),read(b),g1.add(a,b),g1.add(b,a);
	g1.dfs1(1),read(m);
	while (m--){
		read(cnt);
		for (int i=1;i<=cnt;i++) read(poi[i]),seq[i]=poi[i],bo[poi[i]]=1,bel[poi[i]]=poi[i];
		sort(poi+1,poi+1+cnt,cmp),work();
	}
	return 0;
}

/*
10
2 1
3 2
4 3
5 4
6 1
7 3
8 3
9 4
10 1
5
2
6 1
5
2 7 3 6 9
1
8
4
8 7 10 3
5
2 9 3 5 8
*/


你可能感兴趣的:(树形DP,虚树)