Luogu P2664 樹上遊戲

2022-07-03 22:18:19 字數 3651 閱讀 5688

\(lrb\) 有一棵樹,樹的每個節點有個顏色。給乙個長度為 \(n\) 的顏色序列,定義 \(s(i,j)\) 為 \(i\) 到 \(j\) 的顏色數量。以及

\[sum[i]=\sum_^n s(i,j)

\]現在他想讓你求出所有的 \(sum[i]\)

第一行為乙個整數 \(n\) ,表示樹節點的數量

第二行為 \(n\) 個整數,分別表示 \(n\) 個節點的顏色 \(c[1],c[2]……c[n]\)

接下來 \(n-1\) 行,每行為兩個整數 \(x,y\),表示 \(x\) 和 \(y\) 之間有一條邊

輸出 \(n\) 行,第 \(i\) 行為 \(sum[i]\)

5

1 2 3 2 3

1 22 3

2 41 5

10911

912

\(sum[1]=s(1,1)+s(1,2)+s(1,3)+s(1,4)+s(1,5)=1+2+3+2+2=10\)

\(sum[2]=s(2,1)+s(2,2)+s(2,3)+s(2,4)+s(2,5)=2+1+2+1+3=9\)

\(sum[3]=s(3,1)+s(3,2)+s(3,3)+s(3,4)+s(3,5)=3+2+1+2+3=11\)

\(sum[4]=s(4,1)+s(4,2)+s(4,3)+s(4,4)+s(4,5)=2+1+2+1+3=9\)

\(sum[5]=s(5,1)+s(5,2)+s(5,3)+s(5,4)+s(5,5)=2+3+3+3+1=12\)

對於 \(40\%\) 的資料,\(n\le 2000\)

對於 \(100\%\) 的資料,\(1\le n,c[i]\le 10^5\)

點分治點分治的神仙題哇天哪,乙個個題解看得我那叫乙個懵。我還是看神仙的題解才懂的,我這篇題解希望能讓您們理解神仙的做法。

首先瞅一眼資料範圍 \(10^5......\) 是 \(nlog\) 或 \(nlog^2\) 的標準範圍,那麼很顯然我們不能統計路徑,而是應該統計顏色對路徑們的貢獻。則第一時間發現和樹上路徑有關,自然上點分。

那麼點分怎麼搞呢?說來就不簡單啊 \(......\) ,對於當前處理的這顆子樹,我們記 \(cnt[i]\) 為它的所有子樹內到他的路徑中包含顏色 \(i\) 的路徑條數,那麼當處理到一顆子樹內的時候,我們統計得到其他子樹貢獻的所有 \(cnt\) 陣列,那麼對於該子樹內的乙個點,先不考慮它到根節點的路徑上的顏色,則其它子樹內的顏色對當前節點 \(x\) 的貢獻肯定為 \(\sum cnt[i]\) (您別告訴我這個看不出來就成) 。

那麼這樣一來就有問題如下:

我們先解決 \(cnt\) 陣列,這個很簡單,我們先統計每顆子樹的 \(cnt\) 陣列,設 \(col[i]\) 為點 \(i\) 的顏色,則每當我們訪問到乙個之前沒有出現過的顏色 \(col[i]\) ,那麼 \(cnt[col]+=size[i]\) 。我想這個很好理解,因為子樹內的每個節點到根節點的路徑上都經過了節點 \(i\) 。所以我們只需要開個 \(book\) 陣列記錄 \(book[i]\) 為當前節點到根節點路徑上顏色為 \(i\) 的節點個數,進入 \(i\) 節點的時候 \(book[col[i]]++\) ,退出時 \(--\) 即可。

如何減去當前處理的子樹對 \(cnt\) 陣列的貢獻呢?當然是在處理這顆子樹之前再掃一遍,把貢獻去掉,處理完之後又掃一遍,再加回來 = = 。我也覺得很蠢。

第二個問題稍稍難想一點,設當前分治的子樹為 \(now\) ,我們先 \(dfs\) 統計貢獻,對於當前結點 \(i\) ,我們記錄 \(tot=\sum_ cnt[col[j]]\) ,也就是將 \(i\) 到根節點路徑上的顏色在 \(cnt\) 陣列中對應貢獻記錄下來,由於其他子樹內這些顏色的路徑到點 \(i\) 也會經過這些點,我們不能重複計算貢獻,那就只能犧牲 \(cnt\) 陣列的貢獻了 \(qwq\) 。

接著,如果統計的過程中遇到了乙個新的顏色,那麼 \(tot+=cnt[col[i]]\) ,可以預料到的是,這個新顏色對它的子樹內每個節點的貢獻肯定是 \(size[root]-size[now]\) ,也即當前分治子樹外所有節點都可以通過當前節點到達當前節點子樹內的節點,我們帶著這個貢獻往下 \(dfs\) ,設其為 \(num\) ,在設乙個 \(sum=\sum cnt[i]\) 來表示總貢獻,那麼當我們 \(dfs\) 到點 \(i\) 的時候就變成了如下流程:

然後分治遞迴處理更多的子樹就完了!

不理解可以看下**(碼風仙,無空格,不過有注釋)

#include #include using namespace std;

int n, col[100001];

int rt, sum, top, y, maxs[100001], size[100001], now[100001], cbook[100001],

cnt[100001];

int head[100001], nx[200001], to[200001];

bool vis[100001], book[100001];

long long sum, ans[100001];

void add(int u, int v, int d)

void getrt(int x, int fa)

maxs[x] = max(maxs[x], sum - size[x]);

if (maxs[x] < maxs[rt]) rt = x;

}void getsize(int x, int fa)

void getcol(int x, int fa) //如果沒出現過,我們的貢獻就 +=size

if (!book[col[x]])

cbook[++top] = col[x],

book[col[x]] = true; //記錄一下當前分治的部分總共有哪些顏色

now[col[x]]++; //出現次數變更

for (int i = head[x]; i; i = nx[i])

if (to[i] != fa && !vis[to[i]]) getcol(to[i], x);

now[col[x]]--;

}void delcol(int x, int fa)

now[col[x]]++;

for (int i = head[x]; i; i = nx[i])

if (to[i] != fa && !vis[to[i]]) delcol(to[i], x);

now[col[x]]--;

}void count(int x, int fa, int num, long long tot)

void work(int x)

ans[x] += sum - cnt[col[x]] + size[x];

for (int i = 1; i <= top; i++) //將出現過的顏色的貢獻統統刪掉

book[cbook[i]] = false, cnt[cbook[i]] = 0;

}void solve(int x)

}int main()

maxs[rt = 0] = sum = n;

getrt(1, 0); //求重心

solve(rt);

for (int i = 1; i <= n; i++) printf("%lld\n", ans[i]);

}

LuoGu P2664 樹上遊戲

portal 這題真的好.看到樹上路徑,腦子裡就要點分治 這一題對於每個點都要計算一遍,如果暴算實在不好算,這樣我們就可以考慮算貢獻.直接計算每種顏色的貢獻.因為一條過重心的路徑中,可能兩邊都會有相同顏色,那麼我們就只考慮當前點到分治中心的鏈上.然後把多算的這條鏈上的東西刪除就可以了.copy fr...

P2664 樹上遊戲

分析 點分治。首先關於答案的統計轉化成計算每個顏色的貢獻。1 計算從根出發的路徑的答案 如果某乙個顏色是從根到這個點的鏈上的第一次出現的,那麼這個顏色會對根產生siz x 個貢獻。根連向它子樹的任意乙個點的路徑都包含這個顏色 2 計算子樹內每個點過根的路徑答案 記錄乙個陣列sum i 表示從根出發包...

P2664 樹上遊戲

題面 作為一道經典的點分治題目,此題能很好的考察對點分治的運用。個人認為點分治的本質在於 對於樹上近乎n2 的路徑詢問,通過有效的 劃分,使之能在穩定的時間內通過儲存資訊 獲取資訊的經典方式來求 出答案。由此看出點分治的關鍵在於儲存資訊與獲取資訊的方式。點分治的模板套上之後我們只需要考慮的是子樹與子...