[codeforces 1187E] Tree Painting 换根dp
总目录详见https://blog.csdn.net/mrcrack/article/details/105199636
在线测评地址https://codeforces.ml/problemset/problem/1187/E
Problem | Lang | Verdict | Time | Memory |
---|---|---|---|---|
1187E - Tree Painting | GNU C++11 | Accepted | 109 ms | 13400 KB |
以上述数据进行手工模拟,暴力模拟方式如下,
sz[]对应 保存该子树的节点数, d[]对应 保存每结点子树的权值总和 即 题目所求
换根dp算法思路同https://blog.csdn.net/ccsu_cat/article/details/94397592
tmp=d[u]-d[v]-sz[v];
d[v]=d[v]+tmp+n-sz[v];
解释如下:
思路:我们发现如果我们定好了第一个,那么总权值是一个定值,那么问题转化为,枚举每一个点为涂色起点所能获得的总权值,答案是最大的那个,设d[u] 为 u 子树中,先涂 u 点所能获得的总权值,sz[u] 为 u 子树大小,那么转移方程:d[u] = d[son] + sz[u],我们先一遍dfs求出以 1 为根且为涂色起点的答案,接下来再用一个dfs进行换根dp,对于 u 的儿子 v,我们要把根转移到 v,首先断开u v 的连接:d[u] =d[u] - sz[v] -d[v],然后以 v 为根再连接:d[v] =d[v] + d[u] + n - sz[v]。
n-sz[v]说明摘自http://www.machaoqiang.cn/?p=1022
当然,肯定不是以1为根就是最优了,我们尝试换根,将u的孩子v当做根,画图可以看出以v为根的总分就会少了sz[v]这一段,因为以根节点开始染色的第一次答案肯定是n。但是又多了以u为根的子树(此时u相对于v来说是孩子),这一段肯定不能用sz[u]表示,因为一共有n个点,所以这一段可以用n-sz[v]表示。
换根dp算法模拟如下
AC代码如下
#include <cstdio>
#include <algorithm>
#define LL long long
#define maxn 200010
using namespace std;
int n,head[maxn],cnt;
LL sz[maxn],d[maxn],ans;//d[]保存每结点子树的权值总和 即 题目所求;sz[]保存该子树的节点数
struct node{
int to,next;
}e[maxn<<1];
void add_edge(int u,int v){//邻接表
cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void init(){
int i,u,v;
scanf("%d",&n);
for(i=1;i<n;i++){
scanf("%d%d",&u,&v);
add_edge(u,v),add_edge(v,u);//无向图
}
}
void dfs1(int u,int fa){//以1为根节点,计算sz[],d[]
int v,b;
sz[u]=1;//每个子树的节点数初始化为1
for(b=head[u];b;b=e[b].next){
v=e[b].to;//用 v 表示 子节点
if(v==fa)continue;//如果 u 的子节点 v 为 u 的父节点 fa;(fa已经涂色) 则跳过此子节点
dfs1(v,u);//求子节点的权值d[v] 和 子树节点数sz[v]
sz[u]+=sz[v];//把每一个子树大小相加
d[u]+=d[v];//该点子节点权值 相加
}
d[u]+=sz[u];//该点权值 加 所有子节点的权值
}
void dfs2(int u,int fa){//换根dp
int v,b;
LL tmp;
ans=max(ans,d[u]);//在u节点为第一次涂色情况时的权值大小 ,取大值更新ans
for(b=head[u];b;b=e[b].next){//换根
v=e[b].to;
if(v==fa)continue;//父节点已经算过 跳过
tmp=d[u]-d[v]-sz[v];
d[v]=d[v]+tmp+n-sz[v];
dfs2(v,u);//继续向子节点扫描
}
}
int main(){
init();
dfs1(1,0);
dfs2(1,0);
printf("%lld\n",ans);
return 0;
}