題目鏈接:https://acm.hdu.edu.cn/showproblem.php?pid=7136
題意:
給一顆n個點的樹,每個點標記為1到n,每個點有自己的權值ai(保證不一樣)。有只猴子可以從u點跳到v點,當且僅當u點到v點的最短路徑上權值最大的點是v點。問猴子從k點開始(k∈【1.n】)最大可以跳多少個點。對於樣例二,猴子在點1時,它可以跳 1 -> 3 -> 2 -> 4 共4個點。
思路:
第一眼看過去就想到換根DP,推了半天不會轉移,然后想着從最大值開始dfs維護單調棧,發現還是搞不了,太菜了QAQ
考慮最大權值的點,顯然該連通塊的每個點都可以跳到該點,該點對該連通塊內所有點的貢獻為1(所有點都可以跳到該點上)
然后考慮次大權值的點,如果某個點到次大權值點的最短路徑上有最大值,顯然那個點無法到達次大值點,所以我們干脆把該連通塊最大值點的所有連邊都砍了,分成若干了連通塊,每個連通塊都有自己的最大值,這樣就可以去求別的連通塊對答案的貢獻,不斷重復這個過程就可以維護出每個點的最終答案。如圖:(紅色數字表示該點能跳多少步,黑色數字表示節點權值)
這個遞歸過程不好實現,所以我們考慮反過來進行這個過程。
按權值從小到大枚舉點u,首先把點u單獨放入一個連通塊(區分一下沒遍歷到的點),然后遍歷它的相鄰節點,如果有相鄰節點在另一個連通塊內,那么說明另一個連通塊是因為點u砍斷了點u的出邊而產生的(因為權值從小到大枚舉,當前的權值比枚舉過的權值大),把他們重新合成一個連通塊,其中點u作為父節點,另一個連通塊的根作為子節點,這就有點像並查集存在一個上下級關系。枚舉完后,並查集就形成了一顆新樹,每個節點的深度即為答案(根節點深度為1)。
模擬過程:假設一開始的圖是: 3 - 1 - 4 - 2
從小到大枚舉權值:
第一個是1,相鄰的點為3,4,都沒有遍歷過,所以1單獨成一個連通塊。
第二個是2,相鄰的點為4,都沒有遍歷過,所以2單獨成一個連通塊。
第三個是3,相鄰的點為1,說明1所在連通塊是因為3而拆出來的,現在合回去。
第四個是4,相鄰的點為1,2,說明1,2的連通塊是因為4拆出來的,連通0塊1的根是3,連通塊2的根是2,合起來。
這顆就是新的樹了,根據並查集的關系建出來的樹。
如果看着這顆樹,按一開始的思路進行拆連通塊統計貢獻,就會發現,每在一個連通塊上刪一個最大值,就相當於在這顆樹上刪去那個點,然后這個點包含的子樹貢獻全部+1,和一開始的想法一樣,刪着刪着就會發現節點深度就是我們維護的貢獻,妙啊。
反思:這道題刪一個點形成多個連通塊,而並查集維護的是連通塊,所以應該往並查集這個方向想?有沒有大佬指點下思路應該怎么往這個方向靠近啊....
代碼:
#include <bits/stdc++.h> #define ll long long using namespace std; const int maxn = 1e5 + 7; vector<int> E[maxn],tu[maxn]; int n,fa[maxn],dep[maxn]; struct node { int x,val; }a[maxn]; bool cmp(node a,node b) { return a.val < b.val; } int fid(int x) { return x == fa[x] ? x : fa[x] = fid(fa[x]); } void dfs(int u,int fa) { dep[u] = dep[fa] + 1; for (auto v:tu[u]) { if(v == fa) continue; dfs(v,u); } } void solve() { int x,y; scanf("%d",&n); for (int i=1; i<=n; ++i) { E[i].clear(); tu[i].clear(); fa[i] = dep[i] = 0; } for (int i=1; i<=n-1; ++i) { scanf("%d%d",&x,&y); E[x].push_back(y); E[y].push_back(x); } for (int i=1; i<=n; ++i) { scanf("%d",&a[i].val); a[i].x = i; } sort(a+1,a+1+n,cmp); for (int i=1; i<=n; ++i) { fa[a[i].x] = a[i].x; for (auto v:E[a[i].x]) { if(fa[v]) { int V = fid(v); fa[V] = a[i].x; tu[V].push_back(a[i].x); tu[a[i].x].push_back(V); } } } dfs(a[n].x,0); for (int i=1; i<=n; ++i) printf("%d\n",dep[i]); } int main() { int t; scanf("%d",&t); while(t--) { solve(); } return 0; }