Test 6.29 T2 染色

问题描述

于是 CJK 轻轻松松就切了第一题。“好,那么来看看第二题吧。” JesseLiu 大手一挥,CJK 眼前立刻出现了一棵有 n 个节点的树。“现在,你将要为这颗树染色。你每对一个点染色,将要花费相应的费用。当你对某一点染色后,与它相邻的点也会被染色。你的任务是:在 1s 之内,求出花费最少的染色方案,使得树上所有的点都被染色。”

输入格式

第一行 n,表示树中结点的数目。 接下来的 n 行,每一行包含的整数依次为:点的标号 i(1<=i<=n),在点 i 染色费用 k,点 i 的子结点数目 m,接下来 m个数为区域 i 的子结点编号。注意:i 不一定按顺序给出,根节点不一定为 1。

输出格式

最小费用。

样例输入输出

样例输入1

6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0

样例输出1

25

样例输入2

5
1 12 2 2 3
2 10 2 4 5
3 5 0
4 11 0
5 20 0

样例输出2

15

解析

显然,这是一道树形DP题。一个节点要被染色,有且仅有以下3种情况:

  • 这个点被某个子节点覆盖。
  • 这个点被父节点覆盖。
  • 这个点自己被选中。

(f[i])表示以(i)为根的子树被完全覆盖的最小代价,那么以上三种情况分别设为(f[i][0],f[i][1],f[i][2])。我们分别讨论3种情况的状态转移方程。

设当前节点为(i),父节点为(fa),任意子节点为(j)

对于第一种,我们必须保证节点的子节点有一个被覆盖且自己没有被覆盖。那么(f[i][0])只能由(f[j][0])(f[j][2])推得。为了保证有子节点被染色且答案最小,我们再分两种情况:若存在(j)使(f[j][2]<f[j][0]),那么就不用管。若不存在,则将(f[j][2]-f[j][0])最小的(j)的贡献强制转为(f[j][2])。状态转移方程如下:

[f[i][0]=sum min(f[j][0],f[j][2])+min(f[j][2]-f[j][0],0) ]

对于第二种,(i)的子节点只能被自己的儿子覆盖。注意这里不需要将父亲的代价计入,由于该状态只对第三种情况产生影响,在回到上一层时会计算的。状态转移方程如下:

[f[i][1]=sum f[j][0] ]

对于第三种,则是(j)的三种状态取最小值再加上自己的代价。状态转移方程如下:

[f[i][2]=sum min(f[j][0],f[j][1],f[j][2])+w[i] ]

注意叶子节点的边界。

代码

#include <iostream>
#include <cstdio>
#define N 100002
using namespace std;
const int inf=1<<30;
int head[N],ver[N*2],nxt[N*2],l;
int n,i,j,w[N],f[N][3];
void insert(int x,int y)
{
	l++;
	ver[l]=y;
	nxt[l]=head[x];
	head[x]=l;
}
void dfs(int x,int pre)
{
	int min1=inf,id=0,sum0=0,sum1=0,sum=0;
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		if(y!=pre){
			dfs(y,x);
			int tmp=f[y][2]-f[y][0];
			if(tmp<0) min1=0;
			else if(tmp<min1) min1=tmp,id=y;
			int t1=min(f[y][0],f[y][2]);
			sum0+=t1;
			sum1+=min(t1,f[y][1]);
			sum+=f[y][0];
		}
	}
	f[x][0]=sum0+min1;
	if(f[x][0]==inf) f[x][0]=w[x];
	f[x][1]=sum;
	f[x][2]=sum1+w[x];
}
int main()
{
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	cin>>n;
	for(i=1;i<=n;i++){
		int x,y,m;
		cin>>x;
		cin>>w[x]>>m;
		for(j=1;j<=m;j++){
			cin>>y;
			insert(x,y);
			insert(y,x);
		}
	}
	dfs(1,-1);
	int ans=min(f[1][0],f[1][2]);
	cout<<ans<<endl;
	fclose(stdin);
	fclose(stdout);
	return 0;
}

总结

这道题做错,是因为DP的情况没有考虑完整导致错误。下次一定要注意。

原文地址:https://www.cnblogs.com/LSlzf/p/11111677.html