• CF1187E Tree Painting


    前言

    一道挺好的思维题。

    本人蒟蒻一枚,这题是本人独立A掉的第一道洛谷蓝色难度的CF思维题,极具纪念意义。故本人会将思考过程尽量全面地记录下来,以观察思考的不足之处。

    题意简述

    题目链接

      给定一棵树,初始时全为白点,要求按以下方法进行n次染色操作:

      1.第一次可以任意选择一个节点染成黑色。

      2.以后每一次任意选择一个与黑点有直接边相连的白点,将其染成黑色。

      定义每次染色可获得的权值为该次染色时,所染的白点所在连通块的大小。

      要求n次染色后所获得的权值之和最大,求这个最大值。

    算法概述

      最优化问题,优先考虑DP。

      首先很容易发现一个很显然的性质:当第一次染色的节点确定下来之后,以后染色所能获得的权值之和就确定了。

      暴力枚举第一次染色的节点显然不行,所以可以考虑用dp来计算这个权值之和。

      f[u]表示以u为一号染色点时所能获得的权值之和。

      我们关键来看这个f[u]如何计算。不难发现f[u]可分成两部分:

      ①从u出发向以u为根的子树染色,所能获得的权值之和。

      ②从u出发向u的父亲方向染色,所能获得的权值之和。

      子树的信息还是比较方便统计的,主要难点在于第②部分,首先显然u也是在其父节点的子树当中的,所以我们发现一个点的子树信息可能要重复被用到,故我们再开一个数组。

      dp[u]表示从u出发向以u为根的子树染色,所能获得的权值之和。要计算dp[u],只需考虑其每个儿子即可。

      显然dp[u]=∑(dp[v]+siz[v]),其中v为u的儿子节点,siz[u]表示以节点u为根的子树大小。

      所以我们可以自底向上计算出每个点的dp值。

      那么f[u]的第①部分就是dp[u]了。

      再来看第②部分,记u的父节点为fa,考虑f[fa]的组成:一部分是向u染色,另一部分是向其他节点染色。

      所以我们可以在f[fa]中挖掉向u染色的部分,然后再加上从u到fa这一步的值(即将fa这个节点染色的权值)即可。形式化来说,挖掉向u染色的部分即f[fa]-dp[u]-siz[u],再加上从u到fa这一步的值即n-siz[u](总节点数减去子树u的大小)。

      故第②部分的值就等于f[fa]-dp[u]+n-2*siz[u]。

      于是我们就得到了f的状态转移方程:f[u]=dp[u]+f[fa]-dp[u]+n-2*siz[u]=f[fa]+n-2*siz[u]。

      所以我们可以自上往下计算出每个点的f值。

      最后再比较得出全局最大值即可。

      当然,由于我们计算f[u]时并未加上第一步染色u点时的权值,故而最后还应将答案加上n。

      时间复杂度O(n+m)

      根据如上分析,我们发现了最开始思考时的思维漏洞——dp数组其实根本不需要。

      我们只需要先预处理出以1为根时的权值之和,然后以此为边界,根据上面的状态转移方程再在dfs过程中进行递推即可。

      然后我们重新考虑一下f[u]的计算:

      考虑f[fa]的组成:(1)一部分是向u染色,(2)另一部分是向其他节点染色。

      考虑f[u]的组成:(3)一部分是向fa染色,(4)另一部分是向子树染色。

      我们发现(4)是包含在(1)中的,(4)=(1)-siz[u],而(2)是包含在(3)中的,(3)=(2)+n-siz[u]。

      两式一加即可直接得出结果。

      最后这题答案比较大,可能会爆int,故需要开long long。

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const int N=2e5+10;
    struct Edge{
    	int to,next;
    }edge[N<<1];int idx;
    int h[N];
    
    void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}
    
    ll f[N];
    int siz[N];
    int n;
    
    ll dfs1(int p,int fa)
    {
    	ll res=0; //res的值即为dp值 
    	siz[p]=1;
    	for(int i=h[p];~i;i=edge[i].next)
    	{
    		int to=edge[i].to;
    		if(to==fa)continue;
    		res+=dfs1(to,p)+siz[to];
    		siz[p]+=siz[to];
    	}
    	return res;
    }
    
    void dfs2(int p,int fa)
    {
    	for(int i=h[p];~i;i=edge[i].next)
    	{
    		int to=edge[i].to;
    		if(to==fa)continue;
    		f[to]=f[p]+n-2*siz[to];
    		dfs2(to,p);
    	}
    }
    
    int main()
    {
    	memset(h,-1,sizeof h);
    	scanf("%d",&n);
    	for(int i=1;i<=n-1;i++)
    	{
    		int u,v;
    		scanf("%d%d",&u,&v);
    		add_edge(u,v);
    		add_edge(v,u);
    	}
    	
    	f[1]=dfs1(1,0);
    	dfs2(1,0);
    	
    	ll ans=0;
    	for(int i=1;i<=n;i++)ans=max(ans,f[i]);
    	printf("%lld
    ",ans+n);
    	
    	return 0;
    }

      

  • 相关阅读:
    linux拷贝软连接文件
    【知识点】Java常用类库
    Maven之pom.xml配置文件详解
    Java+Bigdata学习路线
    Hadoop界的Hello World!
    JavaEE三大框架的整合
    学完微型服务器(Tomcat)对其工作流程的理解,自己着手写个简单的tomcat
    【数据结构与算法】之链表
    JavaEE项目开发所需要的包(Struts2+Spring5+Hibernate5)
    在Linux(Centos7)系统上对进行Hadoop分布式配置以及运行Hadoop伪分布式实例
  • 原文地址:https://www.cnblogs.com/ninedream/p/13441984.html
Copyright © 2020-2023  润新知