Luogu P5588 小猪佩奇爬树 题解

Luogu P5588 小猪佩奇爬树 题解

写在前面

我发现这个题目题解是少之又少……于是你就看到了本篇题解。

笔者写下这篇文章,一是为了总结,二是希望帮助到更多的人。

希望大家看完这篇文章都能有所收获。

题目传送门 P5588 小猪佩奇爬树

题解部分

最初的思路

一开始我看到本题,就想是不是和 \(LCA\) 有关。

当时我思考只要求出每个颜色的 \(LCA\) 即伸展的最下方节点(包含该颜色的节点)

只要所有颜色在一条链上,我们就可以统计答案。特别的,一个点也算链。

但是实际上并不需要这么复杂,所以我月赛也没有打出本题。

思路改进及代码实现

那么我们先考虑简单的情况,就是没有这个颜色的节点,那么我们答案即为 \(\frac{n*(n-1)}{2}\)

还有就是这个颜色只有一个节点,那么就是统计一个这节点为根的所有子树大小互乘累加。

这个是根据乘法原理得来的,如果你没有理解上面那句话,那么你可以看下面这句代码来理解:

void dfs(int x, int fa) {
    ……;
    size[x] = 1; 
    for(int i = head[x]; i; i = Next[i]) {
        if(ver[i] == fa) continue;
        dfs(y, x);
        ans1[x] += 1LL * size[x] * size[y];
        size[x] += size[y];
        ……;
    } ans1[x] += size[x] * (n - size[x]);
    ……;
}

其中 \(ans1\)即为我们所要的答案(\(size\)统计子树大小)。

我们只需要再开一个额外的数组确认当前颜色的 \(x\) 在哪即可。

那么以上就是一个颜色只有一个节点的情况。


下面重点讨论一个颜色有两个以上节点的情况,那么可能会出现两种情况:

  1. 所有这样的节点都在一条链上,也就是说这个颜色有两个端点。

  2. 所有这样的颜色不在一条链上,也就是说有三个及以上端点。

那么显而易见的,对于第2种情况一定不存在一种合法的解,输出 \(0\) 即可。

所以重点是解决第1种情况。

我们考虑对于一个颜色,什么时候可能会是端点。我们记一个数组 \(cnt\) 表示颜色 \(c\) 到目前为止出现的次数。

那么对于树上操作,一般进行 \(dfs\) ,那么 \(cnt[c]\)(\(c\) 是当前颜色)就极可能直接发生改变。

那么对于每一次改变,我们定义一个变量 \(flag++\),只要最终结果 \(flag=1\) 就极可能是个端点。

还有另一种可能,就是进入当前节点时 \(cnt[c]\) 已经有值,或者当前节点不为当前颜色的最后一个节点,那么也要使 \(flag++\)

具体再解释下 \(flag\) 可能会更好理解:\(flag\) 代表当前节点下子树颜色也为 \(c\) 的个数。

这样好理解了吧?记得最后处理完上面的步骤还要让 \(cnt[c]++\)一次(当前颜色也是 \(c\)\(QAQ\))。

只要最后 \(flag=1\)那么即为端点。第一次进入直接让当前颜色的节点指针赋给 \(nos\) (记录当前颜色从哪开始)

然后答案即为最两端的节点的子树大小之积。

  • 很抽象?看看怎么实现的吧:
int color[N], tot[N], cnt[N], n;
int enos[N], size[N], nos[N];
long long ans1[N], ans2[N];
// ans1 统计一个颜色一个节点的情况 
// ans2 统计一个颜色多个节点的情况
// enos[c] 表示 c 的个数   nos[c] 表示 c 对应的位置
// 至于 nos 的用处过会儿完整代码你就明白
// tot[c] 表示颜色 c 总共的个数 剩下的含义看上面啦
inline void dfs(int x, int fa) {
	int c = color[x], k = cnt[c];
	int flag = 0, pos = 0;
	size[x] = 1;
	
	for(int i = head[x]; i; i = Next[i]) {
		int y = ver[i];
		if(y == fa) continue;
		int lastans = cnt[c];
		dfs(y, x);
		ans1[x] += 1LL * size[x] * size[y];
		size[x] += size[y];
		if(lastans != cnt[c]) flag++, pos = y;
	}
	ans1[x] += 1LL * size[x] * (n - size[x]);
	if(k || cnt[c] != tot[c] - 1) flag++;
	cnt[c]++; // 当前节点颜色是 c
	
	if(flag == 1) { // 端点 
		if(!enos[c]) nos[c] = x;
		else {
			int p = pos ? n - size[pos] : size[x];
			ans2[c] = 1LL * size[nos[c]] * p;
		} enos[c]++;	
	}
}

这样子大家应该可以理解如何解决本题了吧?

那么分析下这个 \(dfs\) 的复杂度,很明显是 \(O(N)\) 的……(为什么某位神仙打了\(O(N \log N)\)的……)

好了下面放上完整代码吧(建议按照思路自己先打,不知道 \(nos\) 怎么用可以看看)

\(Code:\)

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;

int head[N], Next[N << 1], ver[N << 1];
void add(int x, int y) {
	static int cnt = 0;
	ver[++cnt] = y, Next[cnt] = head[x], head[x] = cnt;
	ver[++cnt] = x, Next[cnt] = head[y], head[y] = cnt;
}

int color[N], tot[N], cnt[N], n;
int enos[N], size[N], nos[N];
long long ans1[N], ans2[N];

inline void dfs(int x, int fa) {
	int c = color[x], k = cnt[c];
	int flag = 0, pos = 0;
	size[x] = 1;
	
	for(int i = head[x]; i; i = Next[i]) {
		int y = ver[i];
		if(y == fa) continue;
		int lastans = cnt[c];
		dfs(y, x);
		ans1[x] += 1LL * size[x] * size[y];
		size[x] += size[y];
		if(lastans != cnt[c]) flag++, pos = y;
	}
	ans1[x] += 1LL * size[x] * (n - size[x]);
	if(k || cnt[c] != tot[c] - 1) flag++;
	cnt[c]++;
	
	if(flag == 1) { // 端点 
		if(!enos[c]) nos[c] = x;
		else {
			int p = pos ? n - size[pos] : size[x];
			ans2[c] = 1LL * size[nos[c]] * p;
		} enos[c]++;	
	}
}

int main() {
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) {
		scanf("%d", color + i);
		tot[color[i]]++;
		nos[color[i]] = i;
	}
	for(int i = 1, x, y; i < n; i++) {
		scanf("%d %d", &x, &y);
		add(x, y);
	} 
	dfs(1, 0);
	for(int i = 1; i <= n; i++) {
		if(tot[i] == 0) 
			printf("%lld\n", 1LL * n * (n - 1) >> 1);
		else if(tot[i] == 1)
			printf("%lld\n", ans1[nos[i]]);
		else if(enos[i] == 2)
			printf("%lld\n", ans2[i]);
		else puts("0");
	}
	return 0;
}

写在后面

很好的一道题目,但是因为脑抽又想了很久没打出来……

所以 \(Orz\) ……

posted @ 2019-10-14 12:05  Ning-H  阅读(599)  评论(1编辑  收藏  举报