E. Tree Painting(思维+树形dp+换根dp)

https://codeforces.com/problemset/problem/1187/E


首先一个很重要的点就是选了一个点之后,这颗树就会被分成两个子树,然后选左边和右边就是“独立”的了。先选左边和先选右边就不会有顺序上的影响了。然后对于左边再进行选择又回来刚才的状态。所以说选了一个点后后面的答案就确定了。

所以考虑枚举每个点作为根节点。

f[i]表示以i为根的子树获得的最大值。

f[i]=siz[i](以i为根的子树)+总和(v是i的儿子)f[u].

那么这样暴力是O(n^2)的。比较常见的套路是换根dp。

这里引用下洛谷大佬ifndef的图解。

先考虑以任意一点为根,不妨记为 rt,求出 f 数组。

然后记 gi​ 表示以 i 结点为根时的答案。

显然有grt​=frt​。

然后考虑 g 数组从父亲到儿子的转移。

以样例为例:

我们假设当前以 1 号为根,求出了 f 数组,也就是知道了 g1​ ,然后要求出 g2​ 。

考虑一下答案的组成。

首先考虑 2 号结点的孩子的贡献,也就是图中蓝圈内的部分。这部分显然不会改变,贡献就是 f2​−siz2​ 。

然后考虑父亲方向,也就是图中红圈部分对 g2​ 的贡献。

那么除了以 2 号结点,与 1 一号结点相邻的其他子树都会对答案产生贡献,也就是说,我们只需要用以 1 号结点为根时的权值减去以 2 为根的子树的贡献即可,也就是 g1​−f2​−siz2​ 。(这个siz2是原来1号节点最后加的siz[1]==n里面的部分,我自己做的时候漏了)

不要忘了加上 n ,也就是初始的白色连通块大小。

综合一下上述两种方向的贡献,可以得到:

g2=(f2​−siz2​)+(g1​−f2​−siz2​)+n=g1​+n−siz2​*2 。

推广到所有结点,就可以得到:

gu​=gfatheru​​+n−sizu​*2


博客里还有一篇换根dp。可以两篇一起方便复习。https://blog.csdn.net/zstuyyyyccccbbbb/article/details/108361614

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e5+100;
typedef long long LL;
vector<LL>g[maxn];
LL siz[maxn],f[maxn],g_[maxn],n;
bool vis[maxn];
void dfs1(LL u,LL fa)
{
	vis[u]=true;siz[u]=1;
	for(LL i=0;i<g[u].size();i++)
	{
		LL v=g[u][i];
		if(v==fa||vis[v]) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		f[u]+=f[v];
	}
	f[u]+=siz[u];
}
void dfs2(LL u,LL fa)
{
	for(LL i=0;i<g[u].size();i++)
	{
		LL v=g[u][i];
		if(v==fa) continue;
		g_[v]=g_[u]+n-siz[v]-siz[v];
		dfs2(v,u);
	}
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  cin>>n;
  for(LL i=1;i<n;i++)
  {
  	LL x,y;cin>>x>>y;
  	g[x].push_back(y);g[y].push_back(x);
  }
  dfs1(1,0);
  g_[1]=f[1];
  dfs2(1,0);
  LL ans=-0x3f3f3f3f;
  for(LL i=1;i<=n;i++) ans=max(ans,g_[i]);
  cout<<ans<<endl;
return 0;
}

猜你喜欢

转载自blog.csdn.net/zstuyyyyccccbbbb/article/details/108633543