【题解】【中山市选2009】树

\(\text{Description}\)

给定一棵树,每个节点有一盏灯和一个按钮。如果节点的按扭被按了,那么该节点的灯会从熄灭变为点亮(当按之前是熄灭的),或者从点亮到熄灭(当按之前是点亮的)。并且该节点的直接邻居也发生同样的变化。开始的时候,所有的灯都是熄灭的。请计算最少要按多少次按钮,才能让所有节点的指示灯变为点亮状态。

\(\text{Solution}\)

可以先看一看这道题 P1352 没有上司的舞会 以及 【题解】P1352

对于这道题,我们也可以定义 \(dp_{u,0}\) 为节点 \(u\) 不亮时的最少次数,\(dp_{u,1}\) 为节点 \(u\) 亮时的最少次数,但是我们不好记录是否按了按钮。

所以改为三维:

  • \(dp_{u,0,0}\) 表示节点 \(u\) 不按按钮且不亮;

  • \(dp_{u,0,1}\) 表示节点 \(u\) 不按按钮且亮;

  • \(dp_{u,1,0}\) 表示节点 \(u\) 按按钮且不亮;

  • \(dp_{u,1,1}\) 表示节点 \(u\) 按按钮且亮。

在树形 \(\rm DP\) 时一定要满足子节点 \(v\) 是亮的。

\(u\) 的目标状态 需满足 \(u\) 的原来状态 \(v\) 的原来状态
不按 不亮 \(\to\) 1. 不按 亮 2. 不按 不亮 1. 按 亮 2. 不按 亮
不按 亮 \(\to\) 1. 不按 不亮 2. 不按 亮 1. 按 亮 2. 不按 亮
按 不亮 \(\to\) 1. 按 亮 2. 按 不亮 1. 按 不亮 2. 不按 不亮
按 亮 \(\to\) 1. 按 不亮 2. 按 亮 1. 按 不亮 2. 不按 不亮

状态转移方程:

\(dp_{u,0,0}\gets\min(dp_{u,0,1}+dp_{v,1,1},dp_{u,0,0}+dp_{v,0,1});\)

\(dp_{u,0,1}\gets\min(dp_{u,0,0}+dp_{v,1,1},dp_{u,0,1}+dp_{v,0,1});\)

\(dp_{u,1,0}\gets\min(dp_{u,1,1}+dp_{v,1,0},dp_{u,1,0}+dp_{v,0,0});\)

\(dp_{u,1,1}\gets\min(dp_{u,1,0}+dp_{v,1,0},dp_{u,1,1}+dp_{v,0,0}).\)

这个其实挺难推的。

\(\text{Code}\)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 105;
const int INF = 0x3f3f3f3f;

int cnt;
int head[MAXN], dp[MAXN][2][2];

struct edge
{
	int to, nxt;
}e[MAXN << 1];

void add(int u, int v)
{
	e[++cnt] = edge{v, head[u]};
	head[u] = cnt;
}

void dfs(int u, int fa)
{
	dp[u][0][0] = 0, dp[u][1][1] = 1, dp[u][0][1] = dp[u][1][0] = INF; //初始状态
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == fa)
		{
			continue;
		}
		dfs(v, u);
		int t00 = min(dp[u][0][1] + dp[v][1][1], dp[u][0][0] + dp[v][0][1]); //我们是用上一次的来转移,所以提前存下来。
		int t01 = min(dp[u][0][0] + dp[v][1][1], dp[u][0][1] + dp[v][0][1]);
		int t10 = min(dp[u][1][1] + dp[v][1][0], dp[u][1][0] + dp[v][0][0]);
		int t11 = min(dp[u][1][0] + dp[v][1][0], dp[u][1][1] + dp[v][0][0]);
		dp[u][0][0] = min(t00, INF);
		dp[u][0][1] = min(t01, INF);
		dp[u][1][0] = min(t10, INF);
		dp[u][1][1] = min(t11, INF);
	}
}

int main()
{
	int n;
	while (scanf("%d", &n), n)
	{
		cnt = 0;
		memset(head, 0, sizeof(head));
		for (int i = 1; i < n; i++)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			add(u, v);
			add(v, u);
		}
		dfs(1, 0);
		printf("%d\n", min(dp[1][0][1], dp[1][1][1])); //只选亮的
	}
	return 0;
}
posted @ 2021-08-24 19:43  mango09  阅读(35)  评论(0编辑  收藏  举报
-->