首页 > 其他分享 >P2664 树上游戏 题解

P2664 树上游戏 题解

时间:2023-12-19 12:15:09浏览次数:38  
标签:子树 int 题解 路径 tot 贡献 P2664 MAXN 树上

原题链接:P2664

题意:给定一棵树,每个点都有一个颜色 \(c_{i}\)。对于每一个点 \(i\),求出 \(\sum_{j=1}^{n}s(i,j)\) 的值。其中 \(s(i,j)\) 表示点 \(i\) 到点 \(j\) 的颜色数量。

路径相关,考虑点分治。

假设当前的重心为 \(u\),那么对 \(u\) 自己的贡献就是 \(u\) 到 \(u\) 的所有子树中每一个点的路径上的颜色数量之和,我们可以用 \(cnt_{i}\) 表示包含了颜色 \(i\) 的路径有多少条,则贡献为 \(tot=\sum cnt_{i}\),直接 dfs 一遍即可,如果当前遍历到的点 \(v\) 的颜色在当前路径中是第一次出现,那么 \(cnt_{c_{v}}\) 应该加上 \(v\) 的子树大小(\(v\) 的子树中的每一个点到重心 \(u\) 的路径一定都包含 \(c_{v}\) 这个颜色),否则不加。这个颜色必须是第一次出现才加的原因是:一条路径上如果有多个相同颜色那么总共只会贡献 \(1\)。

然后考虑如何计算 \(u\) 的一颗子树中的一个节点的答案变化。我们可以将贡献拆分为两个部分。

  • 其它子树中的任意一个点到重心 \(u\) 的贡献(路径前一半)

    这一部分的贡献比较好求,可以用 \(u\) 的所有子树中的点到 \(u\) 的贡献 \(tot\) 减去当前 \(v\) 所在子树中的点到 \(u\) 的贡献。算当前子树的贡献的 dfs 方法和上述求 \(u\) 的贡献的方法相同。

  • 重心 \(u\) 到当前子树的这个节点的贡献(路径后一半)

    这一部分可以考虑对 \(v\) 所在的子树进行从上到下遍历,维护一个 \(tag\) 值表示当前的贡献,初始值为 \(0\)。如果当前遍历到的点 \(x\) 的颜色在路径中是第一次出现,那么 \(tag\) 需要加上 \(p-ct_{c_{x}}\),其中 \(p\) 表示 \(u\) 到所有其它子树中的所有点的路径数,\(ct_{c_{x}}\) 表示 \(u\) 到所有其它子树中的点的路径中包含 \(c_{x}\) 这个颜色的路径数。当前的后一半路径一共可以匹配到 \(p\) 条前一半的路径,但是有 \(ct_{c_{x}}\) 条路径已经算了当前颜色 \(c_{x}\) 的贡献了,所以要减掉。

那么对于 \(u\) 的每一棵子树中的每一个点 \(v\),答案应该加上 \((tot+tag\)),\(tot\) 是前一半的贡献,\(tag\) 是后一半的贡献。注意这里的 \(tot\) 应该减去 \(v\) 所对应的子树的大小 \(size\),因为 \(c_{u}\) 的贡献多算了 \(size\) 遍,在算路径前一半的贡献的时候没有减掉。

dfs 是 \(O(n)\) 的,分治带一个 \(\log\),总时间复杂度 \(O(n \log n)\)。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long 
#define inf 0x3f
#define inf_db 127
#define ls id << 1 
#define rs id << 1 | 1
#define re register
#define endl '\n'
typedef pair <int,int> pii;
const int MAXN = 1e5 + 10;
int n,sum[MAXN],head[MAXN],c[MAXN],x,y,Cnt,cnt[MAXN],color[MAXN],tp;
int size[MAXN],tot,top,s[MAXN],col[MAXN],ct[MAXN],cn[MAXN],path;
bool vis[MAXN],vi[MAXN];
struct Node{int u,v,nxt;}e[MAXN << 1];
inline void Add(int u,int v){e[++Cnt] = {u,v,head[u]};head[u] = Cnt;}
inline int Get_size(int u,int father)
{
	if(vis[u] == true) return 0;
	size[u] = 1;
	for(int i = head[u]; ~ i;i = e[i].nxt)
		if(e[i].v != father) size[u] += Get_size(e[i].v,u);
	return size[u];
}
inline int Get_wc(int u,int father,int tot,int &wc)
{
	if(vis[u] == true) return 0;
	int sum = 1,mx = 0;
	for(int i = head[u]; ~ i;i = e[i].nxt)
	{
		int now = e[i].v;
		if(now == father) continue;
		int tmp = Get_wc(now,u,tot,wc);
		sum += tmp,mx = max(mx,tmp);
	}
	if(max(mx,tot - sum) <= tot / 2) wc = u;
	return sum;
}
inline void Get_dist(int u,int father,int *cnt)
{
	if(vis[u] == true) return;
	if(vi[c[u]] == false) color[++top] = c[u],vi[c[u]] = true;
	if(++s[c[u]] == 1) cnt[c[u]] += size[u];
	for(int i = head[u]; ~ i;i = e[i].nxt)
	{
		int now = e[i].v;
		if(now == father) continue;
		Get_dist(now,u,cnt);
	}
	s[c[u]]--;
}
inline void Update(int u,int father,int tag)
{
	if(vis[u] == true) return;
	int val = tag;
	if(++s[c[u]] == 1) val += path - cnt[c[u]];
	sum[u] += tot + val;
	for(int i = head[u]; ~ i;i = e[i].nxt)
	{
		int now = e[i].v;
		if(now == father) continue;
		Update(now,u,val); 
	}
	s[c[u]]--;
} 
inline void calc(int u)
{
	if(vis[u] == true) return;
	Get_wc(u,0,Get_size(u,0),u);
	Get_size(u,0);
	tot = top = 0,Get_dist(u,0,cnt);
	vis[u] = true,tp = top;
	for(int i = 1;i <= top;i++) vi[color[i]] = false;
	for(int i = 1;i <= top;i++)
	{
		tot = tot + cnt[color[i]];
		col[i] = color[i],cn[color[i]] = cnt[color[i]];
	}
	sum[u] += tot;int tmp = tot;
	for(int i = head[u]; ~ i;i = e[i].nxt)
	{
		int now = e[i].v;
		s[c[u]] = 1,top = 0;
		Get_size(now,0);
		Get_dist(now,u,ct),s[c[u]] = 0;
		for(int j = 1;j <= top;j++) vi[color[j]] = false;
		cnt[c[u]] -= size[now],tot -= size[now];
		for(int j = 1;j <= top;j++) cnt[color[j]] -= ct[color[j]],tot -= ct[color[j]];
		path = size[u] - size[now];
		Update(now,u,0);
		cnt[c[u]] += size[now],tot = tmp;
		for(int j = 1;j <= top;j++) cnt[color[j]] = cn[color[j]],ct[color[j]] = 0;
	}
	for(int i = 1;i <= tp;i++) cnt[col[i]] = 0;
	for(int i = head[u]; ~ i;i = e[i].nxt) calc(e[i].v);
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	memset(head,-1,sizeof head);
	cin >> n;
	for(int i = 1;i <= n;i++) cin >> c[i];
	for(int i = 1;i < n;i++) cin >> x >> y,Add(x,y),Add(y,x);
	calc(1);
	for(int i = 1;i <= n;i++) cout << sum[i] << endl;
	return 0;
}

标签:子树,int,题解,路径,tot,贡献,P2664,MAXN,树上
From: https://www.cnblogs.com/Creeperl/p/17913403.html

相关文章

  • [ABC328F] Good Set Query 题解
    复习了一下边带权并查集板子。设\(d_{x}\)表示当前点到它所在连通块根节点的距离。合并点\(x\)和点\(y\)所在两个连通块时需要更新\(d\)。因为将\(x\)点所在连通块的根节点的父亲节点设为了\(y\)点所在连通块的根节点,所以有\(x\toy\toFind(y)=x\toFind(x)\to......
  • P6370 [COCI2006-2007#6] KAMEN 题解
    原题链接:P6370思路题意不多赘述。首先这道题的\(60\)分暴力很好打,直接按题目中的操作做即可,时间复杂度\(O(nr)\)。考虑优化暴力。我们会发现很多次石头的起始点为同一列的情况,其实每一次下落的轨迹是差不多的。具体来讲应该是第一次下落的轨迹一定包含了后面每一次的轨迹。......
  • P4786 [BalkanOI2018] Election 题解
    题意给定一个长度为\(n\)的字符串\(s\),有\(m\)个询问,每次询问最少需要删掉多少个字符才能使\(l\)到\(r\)组成的字符串当中的每一个前缀和后缀都满足C的数量不小于T的数量。思路因为要满足C的数量不小于T的数量,我们不妨设字符C的位置的值为\(1\),字符S的位......
  • P8386 [PA2021] Od deski do deski 题解
    显然是一道计数dp。dp状态应该是最难的一部分了,个人认为这种状态设计得比较巧妙。如果像我刚开始一样设\(dp_{i,j}\)表示序列中一共有\(i\)个数,序列最后一个数为\(j\)的合法方案数的话,那么方程就会变得很不好转移,因为我们不知道当前的\(j\)和之前的某些数能不能匹配上,......
  • C0328 【1005 C组】模拟测试 斜率 题解
    原题链接:斜率。题意在一个平面直角坐标系中,给定\(n\)个点的横纵坐标,求出哪两个点所构成的连线的斜率最接近\(\frac{P}{Q}\)。数据范围:\(n\le1000000\)。思路显然这是一道数学题,不能直接暴力去找答案。首先我们可以弱化一下题目,求出斜率最接近\(y=0\)即\(x\)轴的两......
  • Omkar and Akmar 题解
    题意:有一个\(n\)个点的环,以及两个人。每个人可以向环中任意一个位置放置一个\(A\)或者\(B\),但是相邻的位置不能相同,不能行动者输。问最终的局面有多少种。一个结论是:后手必胜。证明:最终肯定不可能出现两个连续的空格,否则一定可以在其中一个上填\(A\)或\(B\)。所以最多只......
  • Square-free division (easy version) 题解
    题意:给定一个长度为\(n\)的序列,求最少能将这个序列分成多少段使得任意一段中不存在两个数的积为完全平方数。一个小Trick:如果两个数乘起来为平方数,可以先将每个数的平方因子除掉,然后这两个数必然相等。于是这道题被转化为了一个区间不能有相等的值,这就很典了。设\(pos_{a_{i......
  • Animals and Puzzle 题解
    原题链接:CF713D题意:给定一个\(n\timesm\)的地图\(a\),\(a_{i}\)为\(0\)或\(1\)。有\(t\)次询问,每次询问给定一个矩形,求出这个矩形中最大的由\(1\)构成的正方形的边长是多少。首先考虑预处理出\(d_{i,j}\)表示以\((i,j)\)为左上角的最大正方形边长是多少。对于每......
  • C0392 B 【1109 B组】预处理器 题解
    题意:求有多少个长度为\(n\)的数组\(a\)满足以下条件。条件一:\(l_{i}\lea_{i}\ler_{i}\)。条件二:\(a_{i}\)模\(2\)等于\(p_{i}\)。条件三:\(s\le\suma_{i}\let\)。求答案模\(mod\)的值,\(mod\)不一定是一个质数。数据范围:\(n\le13\)。又积累到一......
  • A Simple Task 题解
    这道题比较简单,简述一下思路。考虑状压\(DP\)。设\(dp_{i,j}\)表示走到第\(i\)个点,之前走过的点的状态为\(j\)的环的数量。这里有一个细节,就是我们都钦定每个走过的第一点是整个状态中编号最小的点,这样不会重复计算。考虑如何进行转移。如果当前点的编号比走过的最小编......