zoukankan      html  css  js  c++  java
  • 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
    ", 1LL * n * (n - 1) >> 1);
    		else if(tot[i] == 1)
    			printf("%lld
    ", ans1[nos[i]]);
    		else if(enos[i] == 2)
    			printf("%lld
    ", ans2[i]);
    		else puts("0");
    	}
    	return 0;
    }
    

    写在后面

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

    所以 (Orz) ……

  • 相关阅读:
    推介一款小工具——SwitchHosts
    Postman的使用之进行文件上传
    Postman的使用之普通的提交Post和Get请求
    Postman的安装
    Windows配置基础环境(jdk+tomcat)
    yum clean all大坑解决
    通过代理实现访问内网系统
    批量配置免密登录
    设置JRebel热部署【本地模式】
    使用多线程程序模拟实现单生产者/多消费者问题 (Linux 线程锁实践)
  • 原文地址:https://www.cnblogs.com/Ning-H/p/11670828.html
Copyright © 2011-2022 走看看