P2664 树上游戏

P2664 树上游戏

https://www.luogu.org/problemnew/show/P2664

分析:

  点分治。

  首先关于答案的统计转化成计算每个颜色的贡献。

  1、计算从根出发的路径的答案:如果某一个颜色是从根到这个点的链上的第一次出现的,那么这个颜色会对根产生siz[x]个贡献。(根连向它子树的任意一个点的路径都包含这个颜色)。

  2、计算子树内每个点过根的路径答案:记录一个数组sum[i],表示从根出发包含颜色i的路径的条数(在1中,找到一个第一次出现的颜色,加上这个点的siz即可)。然后假设当前点是x,根为z,x所在的子树为y。x->z的路径上,出现的颜色为Num,那么这Num个颜色由于已经在到根的路径上有了,那么随便选择其它子树内的点构成的路径都包含了这个颜色,贡献为(siz[z]-siz[y])*Num;未出现的颜色的贡献:在y子树外计算多少个点与x构成的路径,包含这个颜色。那么可以sum数组的和得到,但是其中包含的y子树的路径,所以一开始要先减掉。

代码:

  1 #include<cstdio>
  2 #include<algorithm>
  3 #include<cstring>
  4 #include<cmath>
  5 #include<iostream>
  6 #include<cctype>
  7 #include<set>
  8 #include<vector>
  9 #include<queue>
 10 #include<map>
 11 #define fi(s) freopen(s,"r",stdin);
 12 #define fo(s) freopen(s,"w",stdout);
 13 using namespace std;
 14 typedef long long LL;
 15 
 16 inline int read() {
 17     int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
 18     for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f;
 19 }
 20 
 21 
 22 const int INF = 1e9;
 23 const int N = 100005;
 24 
 25 int head[N], nxt[N << 1], to[N << 1], En;
 26 int col[N], siz[N], sk[N];
 27 LL ans[N], cnt[N], sum[N], Sum, Num;
 28 bool vis[N];
 29 int Size, Mn, Root, top;
 30 // cnt[i] 颜色i出现的次数,sum[i]以根为起点,包含颜色i的路径条数,Sum为sum[i]的和。 
 31 
 32 void add_edge(int u,int v) {
 33     ++En; to[En] = v; nxt[En] = head[u]; head[u] = En;
 34     ++En; to[En] = u; nxt[En] = head[v]; head[v] = En;
 35 }
 36 
 37 void getroot(int u,int fa) {
 38     siz[u] = 1;
 39     int cnt = 0;
 40     for (int i=head[u]; i; i=nxt[i]) {
 41         int v = to[i];
 42         if (v == fa || vis[v]) continue; // vis[v]!!!
 43         getroot(v, u);
 44         siz[u] += siz[v];
 45         cnt = max(cnt, siz[v]);
 46     }
 47     cnt = max(cnt, Size - siz[u]);
 48     if (cnt < Mn) { Mn = cnt, Root = u; }
 49 }
 50 
 51 void dfs1(int u,int fa) { // 计算siz,sum,以根为起点的答案。 
 52     siz[u] = 1; cnt[col[u]] ++;
 53     for (int i=head[u]; i; i=nxt[i]) 
 54         if (!vis[to[i]] && to[i] != fa) 
 55             dfs1(to[i], u), siz[u] += siz[to[i]];
 56     if (cnt[col[u]] == 1) 
 57         Sum += siz[u], sum[col[u]] += siz[u], sk[++top] = col[u];
 58     cnt[col[u]] --;
 59 }
 60 
 61 void dfs2(int u,int fa) { // 计算子树内每个点 过根的所有路径的答案。 
 62     cnt[col[u]] ++;
 63     if (cnt[col[u]] == 1) 
 64         Num ++, Sum -= sum[col[u]]; // 只考虑过根的路径,Num记录这个点到根的路径第一次出现的颜色的个数 
 65     ans[u] += Num * Size + Sum; 
 66     // 这些Num个颜色因为到根的路径上已经有这个颜色了,所以和其他的点任意组合的路径都满足有这个颜色;Sum为除了Num个颜色以外的颜色的贡献 
 67     for (int i=head[u]; i; i=nxt[i]) 
 68         if (!vis[to[i]] && to[i] != fa) dfs2(to[i], u);
 69     if (cnt[col[u]] == 1) 
 70         Num --, Sum += sum[col[u]];
 71     cnt[col[u]] --;
 72 }
 73 
 74 void Modify(int u,int fa,int p) {
 75     cnt[col[u]] ++;
 76     for (int i=head[u]; i; i=nxt[i]) 
 77         if (!vis[to[i]] && to[i] != fa) Modify(to[i], u, p);
 78     if (cnt[col[u]] == 1) 
 79         Sum += siz[u] * p, sum[col[u]] += siz[u] * p;
 80     cnt[col[u]] --;
 81 }
 82 void change(int u,int fa,int p) {
 83     Sum += siz[u] * p, sum[col[fa]] += siz[u] * p; // sum[col[fa]]=siz[fa],现在应该减去这棵子树 
 84     cnt[col[fa]] ++; Modify(u, fa, p); cnt[col[fa]] --;
 85 }
 86 
 87 void Calc(int u) {
 88     top = 0; dfs1(u, 0); ans[u] += Sum; // 计算子树内每个点的siz,sum,以根为起点的答案。 
 89     for (int i=head[u]; i; i=nxt[i]) {
 90         int v = to[i];
 91         if (vis[v]) continue;
 92         change(v, u, -1); // 把这棵子树的贡献减去 
 93         Size = siz[u] - siz[v]; dfs2(v, u); // Size = siz[v] !!!,计算子树内的每个点答案。 
 94         change(v, u, 1); // 加回来 
 95     }
 96     Num = Sum = 0;
 97     for (int i=1; i<=top; ++i) cnt[sk[i]] = sum[sk[i]] = 0;
 98 }
 99 void solve(int u) {
100     Calc(u); vis[u] = true;
101     for (int i=head[u]; i; i=nxt[i]) {
102         int v = to[i];
103         if (vis[v]) continue;
104         Size = siz[v], Mn = INF;
105         getroot(v, 0);
106         solve(Root);
107     }
108 }
109 
110 int main() { 
111     int n = read();
112     for (int i=1; i<=n; ++i) col[i] = read();
113     for (int i=1; i<n; ++i) {
114         int x = read(), y = read();
115         add_edge(x, y);
116     }
117     Size = n, Mn = 1e9;
118     getroot(1, 0);
119     solve(Root);
120     for (int i=1; i<=n; ++i) printf("%lld\n",ans[i]);
121     return 0;
122 }

 

posted @ 2018-09-28 16:49  MJT12044  阅读(303)  评论(0编辑  收藏  举报