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


    树上边覆盖问题

    例:luoguP2016 战略游戏

    简述题意:

    每个节点都能放一个士兵,每个士兵能看守与他相邻的所有边,求覆盖所有边最少需要多少士兵?

    Solution:

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

    显然珂推得转移方程:

    [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]保安站岗

    简述题意

    每个保安可以保护自己的点和相邻点,求将树上所有点都覆盖最少所需保安数

    Solution

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

    转移方程:

    [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]) 因为不同于树上边覆盖问题,它可以被自己的儿子覆盖,所以对儿子的要求是:要么是被自己覆盖,要么被自己的儿子覆盖

    但对于 (u) 本身要保证自己的儿子中有一个是被自己覆盖,所以要求出最优的那个儿子 (x) 就好了,可以枚举所有儿子,这里介绍一种数学式子优化

    最优的 (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 搬书

    最后欢迎大家来补充啊,团队私题要是涉及隐私的话可以联系我

  • 相关阅读:
    数据库多表查询,左连接(入门)
    让弹出层始终显示在屏幕正中间
    jq中的ajax合集总结
    ajax之$.getScript()
    Jquery遮罩ShowLoading组件
    jquery中prop()方法和attr()方法的区别
    Bootstrap 响应式实用工具
    VS使用技巧
    ubuntu下postgreSQL安装配置
    基础设施即代码(Infrastructure as Code)
  • 原文地址:https://www.cnblogs.com/Silymtics/p/14073993.html
Copyright © 2020-2023  润新知