DP中的树上边/点覆盖问题

树上边覆盖问题

例:luoguP2016 战略游戏

简述题意:

每一个节点都能放一个士兵,每一个士兵能看守与他相邻的全部边,求覆盖全部边最少须要多少士兵?ios

Solution:

\(f[x][1/0]\) 表示回溯到第 \(x\) 个点时所用士兵数量, \(1\) 表示在这里放一个士兵, \(0\) 表示不放优化

显然珂推得转移方程:spa

\[f[x][1] = 1 + \sum_{v \in son[x]}min(f[v][1],f[v][0]) \]

\[f[x][0] = \sum_{v \in son[x]} f[v][1] \]

Code

#include<iostream>
#include<cstdio>

using namespace std;
const int MAXN = 1610;

struct edge{
	int to, nxt;
}e[MAXN << 1];
int head[MAXN], num_edge;
int n, k;
int f[MAXN][2];

int read(){
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
	return s * w;
}
 
void add_edge(int from, int to){ e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge; }

void dfs(int x, int fa){
	f[x][1] = 1;
	// cout<<x<<endl;
	for(int i = head[x]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == fa) continue;
		dfs(v, x);
		f[x][1] += min(f[v][1], f[v][0]);
		f[x][0] += f[v][1];
	}
}

int main()
{
	n = read();
	for(int i = 1, x; i <= n; ++i){
		x = read(), x++;
		k = read();
		for(int j = 1, v; j <= k; ++j){
			v = read(), v++;
			add_edge(x, v), add_edge(v, x);
		}
	}

	dfs(1, 0);

	printf("%d", min(f[1][1], f[1][0]));

	return 0;
}

树上点覆盖问题

例:P2458 [SDOI2006]保安站岗code

简述题意

每一个保安能够保护本身的点和相邻点,求将树上全部点都覆盖最少所需保安数blog

Solution

\(f[u][0/1/2]\) 表示回溯到第 \(u\) 个点时所用士兵数量, \(0\) 表示在本身这里放一个士兵, \(1\) 表示被儿子覆盖, \(2\) 表示被父亲覆盖游戏

转移方程:get

\[f[u][0] = \sum_{v \in son[u]} min(f[v][0],f[v][1],f[v][2]) \]

\[f[u][1] = f[x][0] + \sum_{v \in son[u] \And \And v != x} min(f[v][0], min[v][1]) \]

\[f[u][2] = \sum_{v \in son[u]} min(f[v][0], f[v][1]) \]

感受 \(f[u][0]\)\(f[u][2]\) 的转移方程都比较显然数学

对于 \(f[u][1]\) 由于不一样于树上边覆盖问题,它能够被本身的儿子覆盖,因此对儿子的要求是:要么是被本身覆盖,要么被本身的儿子覆盖string

但对于 \(u\) 自己要保证本身的儿子中有一个是被本身覆盖,因此要求出最优的那个儿子 \(x\) 就行了,能够枚举全部儿子,这里介绍一种数学式子优化io

最优的 \(x\) 知足 \(f[x][0] - min(f[x][0], f[x][1])\) 最小

参考题解

证实:

由于 \(x\) 知足 \(f[u][1] = f[x][0] + \sum_{v \in son[u] \And \And v != x} min(f[v][0], min[v][1])\)

\(F(u, x) = f[x][0] + \sum_{v \in son[u] \And \And v != x} min(f[v][0], min[v][1])\)

假设 \(x\) 不是最优的, 则必有一个 \(y\) 知足 \(F(u, x) > F(u, y)\)

将这个式子化简得(能够将相同的部分消掉)

\(f[x][0] - min(f[x][0], f[x][1]) > f[y][0] - min(f[y][0], f[y][1])\)

因此有最优的 \(x\) 知足 \(f[x][0] - min(f[x][0], f[x][1])\) 最小

证毕

下面是代码时间:

Code:

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;
const int MAXN = 1e6+6;
const int inf = 0x3f3f3f3f;
struct edge{
	int to, nxt;
}e[MAXN << 1];
int head[MAXN], num_edge;

int read(){
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar();	}
	while(ch >= '0' && ch <= '9') s = (s << 1) + (s << 3) + ch - '0', ch = getchar();
	return s * w;
} 

int n;
int f[MAXN][3];

void add_edge(int from, int to){
	e[++num_edge] = (edge){to, head[from]}, head[from] = num_edge;
}

void dfs(int x, int fa){
	int sson = 0;
	int minn = 988888889;
	for(int i = head[x]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == fa) continue;
		dfs(v, x);
		f[x][0] += min(f[v][0], min(f[v][1], f[v][2]));
		f[x][2] += min(f[v][0], f[v][1]);
		if(f[sson][0] - min(f[sson][0], f[sson][1]) > f[v][0] - min(f[v][0], f[v][1])) sson = v;
	}
	f[x][1] = f[sson][0];
	for(int i = head[x]; i; i = e[i].nxt){
		int v = e[i].to;
		if(v == fa || v == sson) continue;
		f[x][1] += min(f[v][0], f[v][1]);
	}
}

int main()
{
	n = read();
	for(int i = 1, m, u, v; i <= n; ++i){
		u = read(), f[u][0] = read(), m = read();
		for(int j = 1; j <= m; ++j){
			v = read();
			add_edge(u, v), add_edge(v, u);
		}
	}
	f[0][0] = inf;
	dfs(1, 0);
	printf("%d", min(f[1][0], f[1][1]));
	return 0;
}

其余两个树上点覆盖问题例题,稍微改一下输入便可,一个套路随便搞

P2899 [USACO08JAN]Cell Phone Network G

T155737 搬书

最后欢迎你们来补充啊,团队私题要是涉及隐私的话能够联系我

相关文章
相关标签/搜索