HDU 4383 how to paint a tree 树形dp

题意:给出一颗二叉树,顶点颜色要么为黑要么为白,现在想要所有点颜色相同。可以进行两种操作:

1.选择两个顶点,将他们之间的最短路上的顶点颜色翻转,包括这两个顶点

2.选择一棵子树,翻转子树内顶点的颜色。

一次操作会花费1的代价,问最少的代价可以让所有点颜色相同

题解:【下面的描述可能看起来字很多,但只是为了更详细地描述这个转移过程,请耐心地阅读】

对于一个点初始颜色为col,对立颜色为rev

\(dp[r][col][0]\)表示要把r的子树染成col,且不需要连接父节点的1操作

\(dp[r][col][1]\)表示要把r的子树染成col,r节点为rev,存在需要父节点的1操作

\(dp[r][rev][0]\)表示要把r的子树染成rev,且不需要连接父节点的1操作

\(dp[r][rev][1]\)表示要把r的子树染成rev,r节点为col,存在需要父节点的1操作

初始状态为叶子节点的状态,即对于所有叶子节点,有

\(dp[r][col][0]=0\)

\(dp[r][col][1]=1\) r点颜色为col,需要先用1次1/2操作变成rev,才符合当前为rev,需要连接父亲节点的1操作的定义

\(dp[r][rev][0]=1\) r点为col,需要一次2操作变成rev

\(dp[r][rev][1]=0\) r点为col,不需子树内的改变

在dfs时,按顺序遍历子树的时候顺便统计以下的状态

1.\(sum=\sum dp[v][col][0]\)

sum可以转移到:

\(sum=>dp[r][col][0]\)(下统一用=>表示更新)

\(sum+1=>dp[r][rev][0]\),用一次操作2

2.\(dps[0]=\sum dp[v][rev][0]\)

dps[0]可以转移到:

i.\(dp[r][col][0]\),此时根节点r是col,其他点为rev,所以先用操作2反转一次,再让根节点用一次操作1

\(dps[0]+2=>dp[r][col][0]\)

ii.\(dp[r][col][1]\),此时根节点r是col,其他点位rev,用操作2反转一次可以达到要求

\(dps[0]+1=>dp[r][col][1]\)

iii.\(dp[r][rev][0]\),此时根节点为col,其他点为rev,单独对根节点用操作1

\(dps[0]+1=>dp[r][rev][0]\)

iiii.\(dp[r][rev][1]\),这个状态是指根节点为col,其他点为rev,显然不用任何操作就符合该状态了

\(dps[0]=>dp[r][rev][1]\)

3.\(dps[1]\)

从子树中选择1棵代价最小的\(dp[v][rev][1]\)(v点颜色为col,子树中存在一条col颜色的路径)和r一起共同进行1操作,也就是说

\(dps[1]=min(dp[v][rev][1])+\sum dp[v][rev][0]\) (这里符号表述的不是很严谨)

dps[1]可以转移到:

i.\(dp[r][col][0]\),想要整棵树变成col,先对r进行2操作,这样,除了选出来的那棵子树内的路径和r构成的路径为rev外,其他都变成了col。然后再对这条颜色为rev的路径进行1操作。所以有

\(dps[1]+2=>dp[r][col][0]\)

ii.\(dp[r][col][1]\),初始r是col的,和dps[1]中选出来的路径构成了一条col的路径,其他点是rev的。这个状态是r为rev,子树目标状态是col,所以在r处用一次操作2,这样r和dps[1]选出的路径就变成了颜色rev,这样就符合\(dp[r][col][1]\)的定义了

\(dps[1]+1=>dp[r][col][1]\)

iii.\(dp[r][rev][0]\),初始r是col的,和dps[1]选出的路径构成了一条col的路径,其他点是rev的,所以只需要一次1操作把col改成rev

\(dps[1]+1=>dp[r][rev][0]\)

iiii.\(dp[r][rev][1]\),初始r是col的,dps[1]的路径也是col的,其他点是rev的,不需要任何操作,已经符合这个状态的定义了

\(dps[1]=>dp[r][rev][1]\)

4.\(dps[2]\)

从子树中选择2棵代价最小的\(dp[v][rev][1]\),这两条颜色为col的路径需要和r一起使用操作1

dps[2]可以转移到:

i.\(dp[r][col][0]\),需要把子树全部变成col,那么就得先在r处用一次操作2,上述路径变成了rev,其他点变成了col,然后在上述路径使用一次操作1,整棵树就是col了

\(dps[2]+2=>dp[r][col][0]\)

ii.\(dp[r][rev][0]\),子树目标变成rev,操作显然就是把上述路径使用一次操作1,变成rev即可

\(dps[2]+1=>dp[r][rev][0]\)

综上,我们写出转移方程

\(dp[r][col][0]=min(sum,dps[0]+2,dps[1]+2,dps[2]+2)\)

\(dp[r][col][1]=min(dps[1]+1,dps[0]+1)\)

\(dp[r][rev][0]=min(sum+1,dps[0]+1,dps[1]+1,dps[2]+1)\)

\(dp[r][rev][1]=min(dps[0],dps[1])\)

代码:

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

const int N=1e4+10;
const int INF=1e9;
vector<int> e[N];
int c[N],n,dp[N][2][2];
void dfs(int u,int f)
{
	int col=c[u],rev=1-col;
	int sum=0,dps[3]={};
	dps[1]=dps[2]=INF;
	for(int i=0;i<e[u].size();i++)
	{
		int v=e[u][i];if(v==f)continue;
		dfs(v,u);
		sum+=dp[v][col][0];
		dps[2]=min(dps[1]+dp[v][rev][1],dps[2]+dp[v][rev][0]);
		dps[1]=min(dps[0]+dp[v][rev][1],dps[1]+dp[v][rev][0]);
		dps[0]+=dp[v][rev][0];
	}
	dp[u][col][0]=min(sum,min(dps[0]+2,min(dps[1]+2,dps[2]+2)));
	dp[u][col][1]=min(dps[1]+1,dps[0]+1);
	dp[u][rev][0]=min(sum+1,min(dps[0]+1,min(dps[1]+1,dps[2]+1)));
	dp[u][rev][1]=min(dps[0],dps[1]);
}
int main()
{
	int cas=0;
	while(~scanf("%d",&n))
	{
		for(int i=1;i<n;i++)
		{
			int a,b;scanf("%d%d",&a,&b);
			e[a].push_back(b);
			e[b].push_back(a);
		}
		for(int i=1;i<=n;i++)scanf("%d",&c[i]);
		dfs(1,0);
		printf("Case %d: %d\n",++cas,min(dp[1][0][0],dp[1][1][0]));
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)e[i].clear();
	}
	return 0;
}
posted @ 2020-09-02 17:34  JWizard  阅读(182)  评论(0)    收藏  举报