BZOJ3572 [Hnoi2014]世界樹


本文版權歸ljh2000和博客園共有,歡迎轉載,但須保留此聲明,並給出原文鏈接,謝謝合作。

 

 

本文作者:ljh2000
作者博客:http://www.cnblogs.com/ljh2000-jump/
轉載請注明出處,侵權必究,保留最終解釋權!

 

Description

世界樹是一棵無比巨大的樹,它伸出的枝干構成了整個世界。在這里,生存着各種各樣的種族和生靈,他們共同信奉着絕對公正公平的女神艾莉森,在他們的信條里,公平是使世界樹能夠生生不息、持續運轉的根本基石。
世界樹的形態可以用一個數學模型來描述:世界樹中有n個種族,種族的編號分別從1到n,分別生活在編號為1到n的聚居地上,種族的編號與其聚居地的編號相同。有的聚居地之間有雙向的道路相連,道路的長度為1。保證連接的方式會形成一棵樹結構,即所有的聚居地之間可以互相到達,並且不會出現環。定義兩個聚居地之間的距離為連接他們的道路的長度;例如,若聚居地a和b之間有道路,b和c之間有道路,因為每條道路長度為1而且又不可能出現環,所卧a與c之間的距離為2。
出於對公平的考慮,第i年,世界樹的國王需要授權m[i]個種族的聚居地為臨時議事處。對於某個種族x(x為種族的編號),如果距離該種族最近的臨時議事處為y(y為議事處所在聚居地的編號),則種族x將接受y議事處的管轄(如果有多個臨時議事處到該聚居地的距離一樣,則y為其中編號最小的臨時議事處)。
現在國王想知道,在q年的時間里,每一年完成授權后,當年每個臨時議事處將會管理多少個種族(議事處所在的聚居地也將接受該議事處管理)。 現在這個任務交給了以智慧著稱的靈長類的你:程序猿。請幫國王完成這個任務吧。

Input

第一行為一個正整數n,表示世界樹中種族的個數。
接下來n-l行,每行兩個正整數x,y,表示x聚居地與y聚居地之間有一條長度為1的雙
向道路。接下來一行為一個正整數q,表示國王詢問的年數。
接下來q塊,每塊兩行:
第i塊的第一行為1個正整數m[i],表示第i年授權的臨時議事處的個數。
第i塊的第二行為m[i]個正整數h[l]、h[2]、…、h[m[i]],表示被授權為臨時議事處的聚居地編號(保證互不相同)。

Output

輸出包含q行,第i行為m[i]個整數,該行的第j(j=1,2…,,m[i])個數表示第i年被授權的聚居地h[j]的臨時議事處管理的種族個數。

Sample Input

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

Sample Output

1 9
3 1 4 1 1
10
1 1 3 5
4 1 3 1 1

HINT

 

N<=300000, q<=300000,m[1]+m[2]+…+m[q]<=300000

 

 

正解:虛樹+DP+倍增

解題報告:

  參考了hzwer的博客

  同虛樹的題目一樣,這道題顯然是先要建出虛樹,然后復雜度就只和關鍵點有關了。

  對於每次詢問建一棵虛樹,建法參見BZOJ2286

  之后我們在虛樹上dfs一遍,得到每個點從屬於哪個節點。之后我們只要統計不在虛樹中的點(在虛樹中的點上面統計了下面不作考慮)。

  考慮虛樹上的某一條邊,如果兩個點同屬於一個節點,那么只要加上兩點之間的未在虛樹中的點數即可。

  假如兩個點不屬於同一節點,那么顯然中間會存在分界點,倍增地找出這個分界點mid,然后兩邊分別計算貢獻就可以了。

  注意我還需要記一個g數組,表示未在上述統計到的節點數,因為有一些點沒有在任何一次討論中被考慮,那么顯然將會與他們在虛樹上的父親節點屬於同一節點,只要把初值設為size,每次在上面討論一次,就把討論的部分刪掉即可。

 

//It is made by ljh2000
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 300011;
const int MAXM = 600011;
int n,ecnt,next[MAXM],to[MAXM],first[MAXN],dfn[MAXN],size[MAXN],deep[MAXN],f[MAXN][19];
int tot,a[MAXN],b[MAXN],belong[MAXN],stack[MAXN],top,g[MAXN],p[MAXN],cnt,c[MAXN];
inline void link(int x,int y){ if(x==0 || x==y) return ; next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; }
inline bool cmp(int x,int y){ return dfn[x]<dfn[y]; }
inline int getint(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}

inline void dfs(int x,int fa){
	size[x]=1; dfn[x]=++ecnt;
	for(int i=first[x];i;i=next[i]) {
		int v=to[i]; if(v==fa) continue;
		f[v][0]=x; deep[v]=deep[x]+1; 
		dfs(v,x); size[x]+=size[v];
	}
}

inline int lca(int x,int y){
	if(deep[x]<deep[y]) swap(x,y); int t=0; while((1<<t)<=deep[x]) t++; t--;
	for(int i=t;i>=0;i--) if(deep[x]-(1<<i)>=deep[y]) x=f[x][i]; if(x==y) return x;
	for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0];
}

inline int getdis(int x,int y){ return deep[x]+deep[y]-deep[lca(x,y)]*2; }

inline void dfs1(int x,int fa){
	int d1,d2; g[x]=size[x]; c[++cnt]=x;
	for(int i=first[x];i;i=next[i]) {
		int v=to[i]; if(v==fa) continue;
		dfs1(v,x); if(belong[v]==0)  continue;
		if(belong[x]==0) { belong[x]=belong[v]; continue; }//注意不要出0了
		d1=getdis(belong[v],x); d2=getdis(belong[x],x);
		if(d1<d2 || (d1==d2 && belong[v]<belong[x])) belong[x]=belong[v];
	}
}

inline void dfs2(int x,int fa){
	int d1,d2;
	for(int i=first[x];i;i=next[i]) {
		int v=to[i]; if(v==fa) continue;
		if(belong[v]==0) { belong[v]=belong[x]; }//沒有歸屬的情況,直接划歸治下!
		else {
			d1=getdis(belong[v],v); d2=getdis(belong[x],v);
			if(d1>d2 || (d1==d2 && belong[v]>belong[x])) belong[v]=belong[x];
		}
		//必須先划分再下傳!不然會有問題!
		dfs2(v,x);	
	}
}

inline void solve(int fa,int u){//討論(fa,u)這條邊
	int son=u,mid=u,nex,d1,d2;
	for(int i=18;i>=0;i--)//son是fa到u的路徑上的第一個節點,也就是一個原樹上的兒子節點 
		if(deep[son]-(1<<i)>deep[fa])
			son=f[son][i];

	g[fa]-=size[son];//減去加入討論的部分,保留仍未討論的部分,是size而不是g!
	if(belong[fa]==belong[u]) { p[belong[fa]]+=size[son]-size[u]; return ; }
	for(int i=18;i>=0;i--) {//尋找分界點
		nex=f[mid][i]; if(deep[nex]<=deep[fa]) continue;
		d1=getdis(nex,belong[fa]); d2=getdis(nex,belong[u]);
		if(d1>d2 || (d1==d2 && belong[u]<belong[fa])) mid=nex;
	}
	p[belong[fa]]+=size[son]-size[mid];
	p[belong[u]]+=size[mid]-size[u];
}

inline void build(){
	tot=getint(); for(int i=1;i<=tot;i++) a[i]=getint(),b[i]=a[i];
	for(int i=1;i<=tot;i++) belong[a[i]]=a[i];
	top=ecnt=cnt=0; sort(a+1,a+tot+1,cmp); if(belong[1]!=1) stack[++top]=1;
	int LCA;
	for(int i=1;i<=tot;i++) {
		if(top==0) { stack[++top]=a[i]; continue; }
		LCA=lca(stack[top],a[i]);
		while(1) {
			if(deep[stack[top-1]]<=deep[LCA]) {
				link(LCA,stack[top]); top--;
				if(stack[top]!=LCA) stack[++top]=LCA;
				break;
			}
			link(stack[top-1],stack[top]);
			top--;
		}
		if(stack[top]!=a[i]) stack[++top]=a[i];
	}
	while(top>1) link(stack[top-1],stack[top]),top--; top--; //記得把剩下的連完邊!

	dfs1(1,0); 
	dfs2(1,0);

	//對於虛樹上的每一條邊進行考慮
	for(int i=1;i<=cnt;i++) 
		for(int j=first[c[i]];j;j=next[j])
			solve(c[i],to[j]);

	for(int i=1;i<=cnt;i++) p[belong[c[i]]]+=g[c[i]];
	for(int i=1;i<=tot;i++) printf("%d ",p[b[i]]); puts("");
	for(int i=1;i<=cnt;i++) p[c[i]]=first[c[i]]=g[c[i]]=belong[c[i]]=0;//需要清空的是所有!不只是tot
}

inline void work(){
	n=getint(); int x,y;
	for(int i=1;i<n;i++) { x=getint(); y=getint(); link(x,y); link(y,x); }
	ecnt=0;	dfs(1,0); ecnt=0; memset(first,0,sizeof(first));
	for(int j=1;j<=18;j++) for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1];
	int q=getint();
	while(q--) build();
}

int main()
{
    work();
    return 0;
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM