題目描述
小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的游戲。«天天愛跑步»是一個養成類游戲,需要玩家每天按時上線,完成打卡任務。
這個游戲的地圖可以看作一一棵包含 nn個結點和 n-1n−1條邊的樹, 每條邊連接兩個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號為從11到nn的連續正整數。
現在有mm個玩家,第ii個玩家的起點為 S_iSi,終點為 T_iTi 。每天打卡任務開始時,所有玩家在第00秒同時從自己的起點出發, 以每秒跑一條邊的速度, 不間斷地沿着最短路徑向着自己的終點跑去, 跑到終點后該玩家就算完成了打卡任務。 (由於地圖是一棵樹, 所以每個人的路徑是唯一的)
小C想知道游戲的活躍度, 所以在每個結點上都放置了一個觀察員。 在結點jj的觀察員會選擇在第W_jWj秒觀察玩家, 一個玩家能被這個觀察員觀察到當且僅當該玩家在第W_jWj秒也理到達了結點 jj 。 小C想知道每個觀察員會觀察到多少人?
注意: 我們認為一個玩家到達自己的終點后該玩家就會結束游戲, 他不能等待一 段時間后再被觀察員觀察到。 即對於把結點jj作為終點的玩家: 若他在第W_jWj秒前到達終點,則在結點jj的觀察員不能觀察到該玩家;若他正好在第W_jWj秒到達終點,則在結點jj的觀察員可以觀察到這個玩家。
輸入輸出格式
輸入格式:
第一行有兩個整數nn和mm 。其中nn代表樹的結點數量, 同時也是觀察員的數量, mm代表玩家的數量。
接下來 n- 1n−1行每行兩個整數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 n1≤Si,Ti≤n,0≤Wj≤n 。
輸出格式:
輸出1行 nn個整數,第jj個整數表示結點jj的觀察員可以觀察到多少人。
輸入輸出樣例
說明
【樣例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
會因為棧溢出而崩潰; 如果使用了上述命令后運行該程序,該程序則不會崩潰。
特別地, 當你打開多個終端時, 它們並不會共享該命令, 你需要分別對它們
運行該命令。
請注意, 調用棧占用的空間會計入總空間占用中, 和程序其他部分占用的內
存共同受到內存限制。
----------------------------------------------------------------------------------------------------分割線
正解:
LCA+桶+差分(也不能說是差分但又和差分類似)
在說正解之前,先聲明一些變量
V1[x] : 以x為LCA的路徑的起點的集合。
Spn[x]: 以x為路徑起點的路徑條數。
V2[x]: 以x為終點的路徑的起點集合。
V3[x]: 以x為LCA的路徑的終點的集合。
另外,首先還要讓讀者摒棄一個觀念。
正解並不是對一個個玩家進行操作。
而是先對全部玩家進行一些預處理,然后用兩個類似的dfs函數對整棵樹處理。
最后再做一些微調,就輸出答案。
由於作者不知以什么樣的方式引進接下來我們要用到的“桶”的概念,我們暫時先來考慮下面一個問題。
給定一條鏈,鏈上每個節點有k個貢獻值,該貢獻值只能向鏈首方向傳播。
對於一個節點i,當且僅當某節點j與i節點的距離等於i節點的貢獻值時,i節點對j節點將產生1的貢獻。
給出鏈上節點的k個貢獻值,最后輸出每個節點能得到多少貢獻。
如果無貢獻應輸出0。
對於這個問題,我們可以從鏈尾節點開始對鏈進行Dfs,每當訪問一個點時,我們可以知道,當前點對哪些點是有貢獻的。
我們設deep[i]為當前節點的深度,G[i]為i點的貢獻值集合。
則對於i點來說,它能對深度為deep[i]+G[i][k]的點產生貢獻。
那么我們用一個數組bucket[i]來維護這個貢獻。
於題意我們能寫出下面偽代碼
Dfs(i)
For p in G[i] ---- ++bucket[deep[i] + p]
Dfs (i.children)
最后只要逆序輸出bucket[i]就行了。
如果對上面的問題理解,那么對於桶這個概念,就能大概理解。
那么,接下來我們進入正題。
對於玩家在樹上的路徑(u,v)
我們可以對其進行拆分。
拆分成: u ---> LCA(u,v) 與 LCA(u,v) ---> v 兩條路徑。
對於這一步,因為我們在一開始已經說明是先對每個玩家進行預處理。
所以在這一步我們選擇Tarjan版本的LCA會更好一些。因為時間復雜度會更少。
不過,用倍增求LCA對於本題來說也是不會卡的(作者親測,時間最長的一個點是0.5s左右)。
我們先考慮 u ---> LCA(u,v) 這條路徑,這是一條向“上”跑的路徑。
對與這條路徑上的點i來說,當且僅當deep[i]+w[i] = deep[u]時,u節點對i節點是有貢獻的。
那么也就是說,只要符合deep[i]+w[i]的全部是玩家起點的點,就能對i點產生貢獻。
所以有下列偽代碼:
Dfs1(i)
·prev = bucket[deep[i]+w[i]]
·Dfs1(i.children)
·bucket[deep[i]] += spn[i]
·ans[i] += bucket[deep[i]+w[i]] - prev
·for k in V1[i] ---do --bucket[deep[k]]
其中
ans[i] 為i節點的最后答案。
Spn與V1數組在文章開頭已經聲明
Prev為剛訪問i節點時bucket[deep[i]+w[i]]里的值。
在這解釋一下偽代碼中不好理解的最后兩條語句。
對於倒數第二條語句,ans[i]加上的其實就是i的子樹對i的貢獻,為什么?
因為我們在處理好子樹之后的,我們已經處理好了對i有影響的節點。
所以我們只要加上先后之間的桶差值就相當於統計了答案。
另外對於最后一條語句,其作用是刪去桶中以i為LCA的路徑的起點深度桶的值。
因為當我們遍歷完i節點的孩子時,對於以i節點為LCA的路徑來說。
這條路徑上的信息對i的祖先節點是不會有影響的。
所以要將其刪去。
在這不打算解釋其他的偽代碼,因為作者認為,在數組和變量給出的情況下。
讀者如果自己能去進行推導與模擬,可能會對這個過程會有更深的了解。
另外,請再次記住文章開頭需要讀者摒棄的概念,這很重要。
在敘述完向上的路徑后,我們再來考慮向下的路徑,即LCA(u,v) --->v。
對於向下走的路徑,我們也思考,在什么條件下,這條路徑上的點會獲得貢獻呢?
很明顯的,當 dis(u,v)-deep[v] = w[i]-deep[i] 等式成立的時候,這條路徑將會對i點有貢獻。
所以,類似的,我們就可以寫出第二個Dfs偽代碼。
Dfs2(i)
·prev = bucket[w[i]-deep[i]]
·Dfs2(i.children)
·for k in V2[i] --do ++bucket[dis(k,i)-deep[i]]
·ans[i] += bucket[w[i]-deep[i]] - prev
·for k in V3[i] --do --bucket[dis(i,k)-deep[k]]
其中
·dis(u,v)表示從u節點到v節點的距離
· V3與V2如文章開頭所定義。
·關於兩條for 語句:第一條是加上以i為終點的路徑的貢獻。
第二條與第一個Dfs中最后一條語句類似。
對於這道題來說,現在我們主要的思路已經完全講完了。
但是,對於實現來說,需要注意以下幾點。
·對於桶bucket來說,我們在計算的過程中其下標可能是負值,所以我們在操作桶時要將其下標右移 MAXN 即點數。
·如果一條路徑的LCA能觀察到這條路上的人,我們還需將該LCA去重。
條件是: if(deep[u] == deep[lca]+w[i])ans[lca]--;
下面貼代碼,LCA用的是倍增,有問題留言。
#include<cstdio> #include<cstring> #include<cmath> #include<vector> #include<algorithm> #define N 300009 #define M 600009 using namespace std; int en,n,m; int w[N],spn[M],bucket[N+M],ans[N]; vector<int> v1[M],v2[M],v3[M]; struct nod{ int u,v,dis,lca; }p[N]; struct edge{ int e; edge *next; }*v[N],ed[M]; inline void add_edge(int s,int e){ en++; ed[en].next = v[s],v[s] = ed+en,v[s]->e =e; } int read(){ int x = 0; char ch = getchar(); while(ch < '0' || ch > '9')ch = getchar(); while(ch >= '0' && ch <= '9'){ x = x * 10 + ch - '0'; ch = getchar(); } return x; } int deep[N],f[N][25],dist[N]; bool use[N]; void dfs(int now,int dep){ use[now] = true; deep[now] = dep; for(int k = 1;k <= 22; k++){ int j = f[now][k-1]; f[now][k] = f[j][k-1]; } for(edge *e = v[now];e;e=e->next) if(!use[e->e]){ f[e->e][0] = now; dist[e->e] = dist[now]+1; dfs(e->e,dep+1); } use[now] = false; } inline int jump(int u,int step){ for(int k = 0; k <= 22; k++) if((step & (1<<k)))u = f[u][k]; return u; } inline int qlca(int u,int v){ if(deep[u] < deep[v])swap(u,v); u = jump(u,deep[u]-deep[v]); for(int k = 22; k >= 0; k--) if(f[u][k] != f[v][k])u = f[u][k],v = f[v][k]; return u == v ? u : f[u][0]; } void LCA(){ //關於LCA的組件 f[1][0] = 1; dfs(1,0); } inline void dfs1(int now){ use[now] = true; int prev = bucket[deep[now]+w[now]+N]; for(edge *e = v[now];e;e=e->next) if(!use[e->e])dfs1(e->e); bucket[deep[now]+N] += spn[now]; ans[now] += bucket[deep[now]+w[now]+N]-prev; int len = v1[now].size(); for(int k = 0; k < len;k++) --bucket[deep[v1[now][k]]+N]; use[now] = false; } inline void dfs2(int now){ use[now] = true; int prev = bucket[w[now]-deep[now]+N]; for(edge *e = v[now];e;e=e->next) if(!use[e->e])dfs2(e->e); int len = v2[now].size(); for(int k = 0; k < len; k++) ++bucket[v2[now][k]+N]; ans[now] += bucket[w[now]-deep[now]+N] - prev; len = v3[now].size(); for(int k = 0; k < len; k++) --bucket[v3[now][k]+N]; use[now] = false; } int main(){ n = read(),m = read(); for(int i = 1; i <= n-1; i++){ int u = read(), v = read(); add_edge(u,v); add_edge(v,u); } for(int i = 1; i <= n; i++)w[i] = read(); LCA(); for(int i = 1; i <= m; i++){ //預處理 int u = read(),v = read(); p[i].u = u; p[i].v = v; p[i].lca = qlca(u,v); p[i].dis = dist[u]+dist[v]-dist[p[i].lca]*2; spn[u]++; v1[p[i].lca].push_back(u); v2[v].push_back(p[i].dis-deep[p[i].v]); v3[p[i].lca].push_back(p[i].dis-deep[p[i].v]); } dfs1(1); //從下至上 dfs2(1); //從上至下 for(int i = 1; i <= m; i++) if(deep[p[i].u] == deep[p[i].lca]+w[p[i].lca]) ans[p[i].lca]--; for(int i = 1; i <= n; i++) printf("%d ",ans[i]); printf("\n"); return 0; }