1. 程式人生 > >NOIP2016天天愛跑步 題解報告【lca+樹上統計(桶)】

NOIP2016天天愛跑步 題解報告【lca+樹上統計(桶)】

題目描述

小c同學認為跑步非常有趣,於是決定製作一款叫做《天天愛跑步》的遊戲。«天天愛跑步»是一個養成類遊戲,需要玩家每天按時上線,完成打卡任務。

這個遊戲的地圖可以看作一一棵包含 nn個結點和 n-1n1條邊的樹, 每條邊連線兩個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號為從11nn的連續正整數。

現在有mm個玩家,第ii個玩家的起點為 S_iSi,終點為 T_iTi 。每天打卡任務開始時,所有玩家在第00秒同時從自己的起點出發, 以每秒跑一條邊的速度, 不間斷地沿著最短路徑向著自己的終點跑去, 跑到終點後該玩家就算完成了打卡任務。 (由於地圖是一棵樹, 所以每個人的路徑是唯一的)

小C想知道遊戲的活躍度, 所以在每個結點上都放置了一個觀察員。 在結點jj的觀察員會選擇在第W_jWj秒觀察玩家, 一個玩家能被這個觀察員觀察到當且僅當該玩家在第W_jWj秒也理到達了結點 jj 。 小C想知道每個觀察員會觀察到多少人?

注意: 我們認為一個玩家到達自己的終點後該玩家就會結束遊戲, 他不能等待一 段時間後再被觀察員觀察到。 即對於把結點jj作為終點的玩家: 若他在第W_jWj秒重到達終點,則在結點jj的觀察員不能觀察到該玩家;若他正好在第W_jWj秒到達終點,則在結點jj的觀察員可以觀察到這個玩家。

輸入輸出格式

輸入格式:

第一行有兩個整數n

nmm 。其中nn代表樹的結點數量, 同時也是觀察員的數量, mm代表玩家的數量。

接下來 n- 1n1行每行兩個整數uu和 vv,表示結點 uu到結點 vv有一條邊。

接下來一行 nn個整數,其中第jj個整數為W_jWj , 表示結點jj出現觀察員的時間。

接下來 mm行,每行兩個整數S_iSi,和T_iTi,表示一個玩家的起點和終點。

對於所有的資料,保證1\leq S_i,T_i\leq n, 0\leq W_j\leq n1Si,Tin,0Wjn 。

輸出格式:

輸出1行 nn個整數,第jj個整數表示結點jj的觀察員可以觀察到多少人。

輸入輸出樣例

輸入樣例#1:
6 3
2 3
1 2 
1 4 
4 5 
4 6 
0 2 5 1 2 3 
1 5 
1 3 
2 6 
輸出樣例#1:
2 0 0 1 1 1 
輸入樣例#2:
5 3 
1 2 
2 3 
2 4 
1 5 
0 1 0 3 0 
3 1 
1 4
5 5 
輸出樣例#2:
1 2 1 0 1 

說明

【樣例1說明】

對於1號點,W_i=0Wi=0,故只有起點為1號點的玩家才會被觀察到,所以玩家1和玩家2被觀察到,共有2人被觀察到。

對於2號點,沒有玩家在第2秒時在此結點,共0人被觀察到。

對於3號點,沒有玩家在第5秒時在此結點,共0人被觀察到。

對於4號點,玩家1被觀察到,共1人被觀察到。

對於5號點,玩家1被觀察到,共1人被觀察到。

對於6號點,玩家3被觀察到,共1人被觀察到。

【子任務】

每個測試點的資料規模及特點如下表所示。 提示: 資料範圍的個位上的數字可以幫助判斷是哪一種資料型別。

【提示】

如果你的程式需要用到較大的棧空問 (這通常意味著需要較深層數的遞迴), 請務必仔細閱讀選手日錄下的文本當rumung:/stact.p″, 以瞭解在最終評測時棧空問的限制與在當前工作環境下調整棧空問限制的方法。

在最終評測時,呼叫棧佔用的空間大小不會有單獨的限制,但在我們的工作

環境中預設會有 8 MB 的限制。 這可能會引起函式呼叫層數較多時, 程式發生

棧溢位崩潰。

我們可以使用一些方法修改呼叫棧的大小限制。 例如, 在終端中輸入下列命

令 ulimit -s 1048576

此命令的意義是,將呼叫棧的大小限制修改為 1 GB。

例如,在選手目錄建立如下 sample.cpp 或 sample.pas

將上述原始碼編譯為可執行檔案 sample 後,可以在終端中執行如下命令運

行該程式

./sample

如果在沒有使用命令“ ulimit -s 1048576”的情況下執行該程式, sample

會因為棧溢位而崩潰; 如果使用了上述命令後執行該程式,該程式則不會崩潰。

特別地, 當你開啟多個終端時, 它們並不會共享該命令, 你需要分別對它們

執行該命令。

請注意, 呼叫棧佔用的空間會計入總空間佔用中, 和程式其他部分佔用的內

存共同受到記憶體限制。


題解

一個晚上,三個多小時,終於把NOIP最難一題拿下,時隔一年,NOIP2016全滿AK

25分演算法

直接暴力,跟著人走,當發現當前節點滿足條件就統計

S為根節點部分分

S為根節點,如果節點i可以觀測到人,那麼首先要滿足w[i]==deep[i],然後以i為根節點的子樹包含多少個終點,i節點的答案就是幾

T為根節點

對於i節點,深度為deep[i]+w[i]的起點才會產生貢獻。那就dfs樹上統計唄: 1、開一個桶bac[x]表示當前深度為x的起點有多少個 2、對於節點i,訪問時先記錄當時的bac[deep[i]+w[i]],再往下遞迴,遞迴完後檢查bac[deep[i]+w[i]],增加了v就說明它的子樹有v個這樣的起點,i的答案就是v

退化為鏈部分分

退化為鏈你能想到什麼? 所有的路徑要麼左走要麼右走 我們只考慮左走【右走類似】 右走時,對於節點i,只有節點i-w[i]為起點時才會產生貢獻。 那就向右掃一遍: 1、同樣開一個桶bac[x]表示掃到現在以x為起點的還未走完的路徑有多少個 2、記錄當前點i的答案bac[i-w[i]] 3、對於在該點結束的路徑的bac[S]--

滿分演算法

滿分演算法其實就是綜上所述。。 先把樹進行lca,路徑分為向上和向下走 1、對於向上走的路徑,在i節點,當deep[i]+w[i]==deep[S]時才會產生貢獻 借用以T為根節點的思想,開一個桶來差分統計就好了 2、對於向下走的路徑,在i節點,當deep[i]-w[i]==deep[T]-dis[S,T]-pret[T]時才會產生貢獻【dis表示路徑長,pret表示若該路徑起點為lca,則走到lca時是什麼時刻,若該路徑起點為自然起點,則pret=0】 3、進行同樣的統計,到i節點時把向上路徑的起點S_up和向下路徑的終點T_up【起點在上面的終點】的對應的bac[ ]++【例如T_up就是bac[deep[T]-dis[S,T]-pret[T]]++】,在訪問結束時將向下路徑的起點S_down和向上路徑的終點T_up對應的另一個端點的統計撤銷【類似於鏈狀部分分的演算法,看不明白可以參照一下】 4、若該點為lca且該點產生了貢獻,貢獻值應該-1,因為統計了兩次 總的來說要注意的地方還是很多的,細節處理要特別注意,3個小時終於A了QAQ,膜拜那些考場AK的dalao  orzorz
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
#define LL long long int
using namespace std;
const int maxn=700005,maxm=2000100,INF=2000000000,P=1000000007;
//快速讀入
inline int read(){
	int out=0,flag=1;char c=getchar();
	while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
	while(c>=48&&c<=57){out=out*10+c-48;c=getchar();}
	return out*flag;
}
//邊資訊建立
int head[maxn],nedge=0;
struct EDGE{
	int to,next;
}edge[maxm];

inline void build(int a,int b){
	edge[nedge]=(EDGE){b,head[a]};
	head[a]=nedge++;
	edge[nedge]=(EDGE){a,head[b]};
	head[b]=nedge++;
}
//lca詢問資訊建立
int Head[maxn],nlca=0;
struct LCA{
	int to,flag,next;
}Lca[maxm];

inline void link(int a,int b){
	Lca[nlca]=(LCA){b,0,Head[a]};
	Head[a]=nlca++;
	Lca[nlca]=(LCA){a,0,Head[b]};
	Head[b]=nlca++;
}

int N,M,w[maxn],rt=0,Siz[maxn],disrt=INF;
//資料讀入
void init(){
	fill(head,head+maxn,-1);
	fill(Head,Head+maxn,-1);
	N=read();M=read();
	int a,b;
	for(int i=1;i<N;i++) build(read(),read());
	for(int i=1;i<=N;i++) w[i]=read();
	for(int i=1;i<=M;i++){a=read();b=read();link(a,b);}
}
//重心為根
void dfs1(int u,int f){
	int to,Min=INF,Max=-INF;
	Siz[u]=1;
	for(int k=head[u];k!=-1;k=edge[k].next)
		if((to=edge[k].to)!=f){
			dfs1(to,u);
			Siz[u]+=Siz[to];
			if(Siz[to]<Min) Min=Siz[to];
			if(Siz[to]>Max) Max=Siz[to];
		}
	if(Min==INF) return;
	if(N-Siz[u]<Min&&f) Min=N-Siz[u];
	if(N-Siz[u]>Max) Max=N-Siz[u];
	if(Max-Min<disrt){disrt=Max-Min;rt=u;}
}

void focus(){
	dfs1(1,0);
	if(!rt) rt=1;
	//cout<<rt<<endl;
}

vector<int> Su[maxn],Sd[maxn],Tu[maxn],Td[maxn];
int pre[maxn],dep[maxn],dis[maxn],S[maxn],T[maxn],pret[maxn],pathi=0,temp;
int lca0[maxn];
bool vis[maxn];

inline int find(int u){
	return u==pre[u] ? u:pre[u]=find(pre[u]);
}
//tarjan_lca演算法割路徑
void dfs2(int u,int f){
	int to;
	pre[u]=u;
	dep[u]=dep[f]+1;
	vis[u]=true;
	for(int k=head[u];k!=-1;k=edge[k].next){
		if((to=edge[k].to)!=f){
			dfs2(to,u);
			pre[to]=u;
		}
	}
	for(int k=Head[u];k!=-1;k=Lca[k].next){
		if(!Lca[k].flag&&vis[to=Lca[k].to]){
			Lca[k].flag=Lca[k^1].flag=true;
			int flag=0,m=find(to);
			if(!(k&1)) {flag=1;to^=u^=to^=u;}
			pathi++;
			if(to==m){
				S[pathi]=to;T[pathi]=u;dis[pathi]=dep[u]-dep[to];
				pret[pathi]=0;
				Sd[to].push_back(pathi);Tu[u].push_back(pathi);
			}else if(u==m){
				S[pathi]=to;T[pathi]=u;dis[pathi]=dep[to]-dep[u];
				Td[u].push_back(pathi);Su[to].push_back(pathi);
			}else{
				lca0[pathi]=m;
				S[pathi]=to;T[pathi]=m;dis[pathi]=dep[to]-dep[m];
				Su[to].push_back(pathi);Td[m].push_back(pathi);
				S[++pathi]=m;T[pathi]=u;dis[pathi]=dep[u]-dep[m];
				pret[pathi]=dep[to]-dep[m];
				Sd[m].push_back(pathi);Tu[u].push_back(pathi);
			}
			if(flag) u=to;
		}
	}
}

void tarjan_lca(){
	dfs2(rt,0);
	/*for(int i=1;i<=pathi;i++){
		printf("%d %d %d\n",S[i],T[i],dis[i]);
	}*/
}

int cntS[maxm],cntT[maxm],ans[maxn];
//樹上統計
void dfs(int u,int f){
	int dS=dep[u]+w[u]+maxn,oriS=cntS[dS],dT=dep[u]-w[u]+maxn,oriT=cntT[dT],to;
	for(unsigned i=0;i<Su[u].size();i++){
		cntS[dep[S[Su[u][i]]]+maxn]++;
	}
	for(unsigned i=0;i<Tu[u].size();i++){
		cntT[dep[T[Tu[u][i]]]-dis[Tu[u][i]]-pret[Tu[u][i]]+maxn]++;
	}
	for(int k=head[u];k!=-1;k=edge[k].next){
		if((to=edge[k].to)!=f){
			dfs(to,u);
		}
	}
	ans[u]=cntS[dS]-oriS+cntT[dT]-oriT;
	for(unsigned i=0;i<Td[u].size();i++){
		cntS[dep[S[Td[u][i]]]+maxn]--;
		//if(u==2) cout<<"lca:"<<lca0[Td[u][i]]<<endl;
		if(lca0[Td[u][i]]==u&&dep[S[Td[u][i]]]+maxn==dS) ans[u]--;
	}
	for(unsigned i=0;i<Sd[u].size();i++){
		cntT[dep[T[Sd[u][i]]]-dis[Sd[u][i]]-pret[Sd[u][i]]+maxn]--;
	}
}
//列印答案
void print(){
	printf("%d",ans[1]);
	for(int i=2;i<=N;i++) printf(" %d",ans[i]);
	printf("\n");
}

int main(){
	init();
	focus();
	tarjan_lca();
	dfs(rt,0);
	print();
	return 0;
}