對於一顆靜態樹,o(nlogn)時間內處理子樹的統計問題。是一種優雅的暴力。
很顯然,樸素做法下,對於每顆子樹對其進行統計的時間複雜度是平方級別的。考慮對樹進行乙個重鏈剖分。雖然都基於重鏈剖分,但不同於樹剖,我們維護的不是樹鏈。
對於每個節點,我們先處理其輕兒子所在子樹,輕子樹在處理完後消除其影響。然後處理重兒子所在子樹,保留其貢獻。然後再暴力跑該點的輕子樹,統計該點子樹的最終答案。如果該點子樹是輕子樹,則消除該子樹的影響,否則保留。用**描述的話,大概是這個流程:
void dfs(int u,int fa,int hvy)
if(son[u])//處理重子樹
dfs(son[u],u,1);
calc(u,fa,1);//暴力統計輕子樹對該點答案的貢獻
ans[u]=res;
if(!hvy)
calc(u,fa,-1);//若點u所在子樹是輕子樹,則逆著原來統計的操作來消除其影響。
}
以上體現大概思想,但遇到具體題目可能有很多細節需要思考。
這個可能不能很容易的明白其為何高效,如何達到o(nlogn)。因此我們考慮每個節點對時間複雜度的貢獻。如果真的明白上述的演算法流程,可以知道我們執行暴力統計的都是對輕邊所連的子樹,因此每個點被遍歷到的次數與它往上到根的輕邊數量有關。而任一點到根的路徑上,輕邊的數量不會超過logn。因此每個點最多被遍歷logn次。這樣想應該好理解很多。
lomsat gelral
這是一道比較經典的入門題,有興趣的可以練手,感受一下演算法的思想,再做下一題。在此不給出**。
下面稍微講一下d. arpa』s letter-marked tree and mehrdad』s dokhtar-kosh paths
感覺這道題還是挺難的,要考慮不少細節。
題意大概就是每條邊有乙個字元(a-v),求每顆子樹下最長的一條簡單路徑,其上的字元可重組成回文串。顯然就是要至多只有乙個字元出現奇數次。
我們把每種字元看作二進位製上的乙個位,即2的冪。則滿足條件的簡單路徑,其邊權異或結果必須為0或2的冪。
因此用到dp和dsu on tree的思想。a[i]表示點i到根的路徑異或值,dp[i]表示a[x]=i的點中,深度最大的x的深度。
對於一顆以u為根的子樹,它的答案路徑(該路徑預設包含u,因此可能不是最終答案)可能是1.u到其子樹中某點的簡單路徑;2.u的兩顆不同子樹中的兩點間的路徑。前者直接判斷來更新答案;對於後者兩顆子樹間的情況,需要不斷更新每個異或值下的最大深度,方便對於跑到的點可以知道此時與它滿足條件的另一點的最大深度,從而得知路徑長來更新答案。然後若該子樹為重子樹,則保留dp資訊,否則重置。
附上**
#include#define dd(x) cout<<#x<<" = "typedef priority_queuebq;
typedef priority_queue,greater> sq;
const int maxn=5e5+10,mod=1e9+7,inf=0x3f3f3f3f;
int a[maxn],dp[maxn*10],sz[maxn],d[maxn],son[maxn],ans[maxn];
vectorg[maxn];
void dfs1(int u,int fa)
}int mx;
bool check(int x,int y)
void cal(int rt,int u)
for (int i=1;i
dsu on tree 樹上啟發式合併
詳解 dsu on tree 樹上啟發式合併 演算法總結 習題 經典例題 題意 一棵樹有n個結點,每個結點都是一種顏色,每個顏色有乙個編號,求樹中每個子樹的最多的顏色編號的和。dsu on tree簡介 在o n 2 的暴力做法中,我們用cnt記錄每種顏色出現的次數,對於每個結點,遍歷這棵子樹上的所...
dsu on tree 樹上啟發式合併
樹上啟發式合併 用於處理離線查詢的子樹問題 第一次先求出重兒子 第二次dfs的過程中,先計算輕兒子,貢獻不保留,再計算重兒子,貢獻保留 最後再加上輕兒子的貢獻,計算出當前節點的值 複雜度為o nlogn 每個節點都有乙個顏色編號 計算一棵以1為根的樹的以每個節點為根的子樹中顏色最多的顏色編號和 這題...
學習總結 Dsu on tree 樹上啟發式合併
rt,這只是一篇小小的總結,以便將來的回顧,並不詳細講 以前也學習過啟發式合併,大概就是像樹形dp一樣在dfs上,將兒子的資訊向父親轉移,容器是map,將兒子的資訊邊轉移邊更新答案,轉移之後便將兒子的容器清空,防止空間超限。不過對於本人而言,雖然思路較為簡便,但是因為有用到map的迭代器,所以這種寫...