link:https://codeforces.com/gym/102253/problem/C
題意:一棵樹,每個點有顏色,求所有路徑上出現的顏色個數之和,基本要求線性。
對每種顏色考慮答案,列舉到顏色c的時候想著把所有顏色c的點拿出來,建立虛樹,這樣總的點數是 \(O(n)\) 的,但建虛樹其實要 \(O(n\log n)\) 的時間,而且建完了的DP似乎也很麻煩。
對這個虛樹考慮,如果顏色c用黑點表示,那麼假設刪去黑點,樹會變成若干連通塊,這個顏色的貢獻就是 \(\binom{n}{2}-\sum_{block}\binom{sz}{2}\)
考慮從上往下模擬這個dfs的過程,對當前的結點x,對每個孩子結點 \(to\),求出 \(sz[to]-\sum\) 從上往下\(c[x]\) 第一次出現的那些點的子樹大小,就能確定對應的連通塊大小,然後統計 \(x\) 的貢獻。
這個子樹大小可以透過一些類似樹上差分的技巧完成:dfs時記錄每種顏色當前還“剩下”幾個點,一開始全部設為 \(n\) 個點,每次搜完某個 \(x\) ,就將其子樹內的點全部刪去——當然 \(x\) 內可能還會有 \(c[x]\) 顏色的其他點,稍微算一下差值就好
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define endl '\n'
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=2e5+5;
int n,c[N],sz[N],cnt[N];
vector<vector<int>> G;
ll ans;
ll C2(int n){return (ll)n*(n-1)/2;}
void dfs1(int x,int fa){
sz[x]=1;
for(auto to:G[x])if(to!=fa){
dfs1(to,x);
sz[x]+=sz[to];
}
}
void dfs2(int x,int fa){
if(x==1)rep(i,1,n)cnt[i]=n;
int cur=cnt[c[x]];
for(auto to:G[x])if(to!=fa){
int before=cnt[c[x]];
dfs2(to,x);
int after=cnt[c[x]];
int del=sz[to]-(before-after);
ans+=C2(del);
cnt[c[x]]-=del;
}
int lst=cnt[c[x]];
cnt[c[x]]-=sz[x]-(cur-lst);
if(x==1)rep(i,1,n)ans+=C2(cnt[i]);
}
int main(){
fastio;
int tc=0;
while(cin>>n){
tc++;
rep(i,1,n)cin>>c[i];
G=vector<vector<int>>(n+1);
ans=0;
rep(i,1,n-1){
int u,v;
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs1(1,-1);
dfs2(1,-1);
cout<<"Case #"<<tc<<": "<<n*C2(n)-ans<<endl;
}
return 0;
}