贪心法求树的最小支配集,最小点覆盖,最大独立集

定义:

最小支配集:对于图G = (V, E) 来说,最小支配集指的是从 V 中取尽量少的点组成一个集合, 使得 V 中剩余的点都与取出来的点有边相连.也就是说,设 V‘ 是图的一个支配集,则对于图中的任意一个顶点 u ,要么属于集合 V‘, 要么与 V‘ 中的顶点相邻. 在 V‘ 中除去任何元素后 V‘ 不再是支配集, 则支配集 V‘ 是极小支配集.称G 的所有支配集中顶点个数最少的支配集为最小支配集,最小支配集中的顶点个数称为支配数.

最小点覆盖:对于图G = (V, E) 来说,最小点覆盖指的是从 V 中取尽量少的点组成一个集合, 使得 E 中所有边都与取出来的点相连.也就是说设 V‘ 是图 G 的一个顶点覆盖,则对于图中任意一条边(u, v), 要么 u 属于集合 V‘, 要么 v 属于集合 V‘. 在集合 V‘ 中除去任何元素后 V‘ 不再是顶点覆盖, 则 V‘ 是极小点覆盖. 称 G 的所有顶点覆盖中顶点个数最小的覆盖为最小点覆盖.

最大独立集: 对于图G = (V, E) 来说,最大独立集指的是从 V 中取尽量多的点组成一个集合,使得这些点之间没有边相连.也就是说设 V‘ 是图 G 的一个独立集,则对于图中任意一条边(u, v), u 和 v 不能同时属于集合 V‘, 甚至可以 u 和 v 都不属于集合 V‘. 在 V‘ 中添加任何不属于 V‘ 元素后 V‘ 不再是独立集,则 V‘ 是极大独立集.称 G 的所有顶点独立集中顶点个数最多的独立即为最大独立集.

求解:

  对于任意图 G 来说,最小支配集, 最小点覆盖, 最大独立集问题是不存在多项式时间的解法,即属于NP问题.但是这里所要求的图 G 是一棵树.可以在多项式的时间内给予解决.在这里利用贪心的思想来解决树上的这三个问题.

(1)最小支配集

贪心策略:首先选择一点为树根,再按照深度优先遍历得到遍历序列,按照所得序列的反向序列的顺序进行贪心,对于一个即不属于支配集也不与支配集中的点相连的点来说,如果他的父节点不属于支配集,将其父节点加入到支配集.

伪代码:

  第一步:以根节点深度优先遍历整棵树,求出每个点在深度优先遍历序列中的编号和每个点的父节点编号.

  第二步:按照深度优先遍历的反向顺序检查每个点,如果当前点不属于支配集也不与支配集的点相连,且它的父节点不属于支配集,将其父节点加入到支配集,支配集中点的个数加 1, 标记当前节点, 当前节点的父节点, 当前节点的父节点的父节点,因为这些节点要么属于支配集(当前点的父节点),要么与支配集中的点相连(当前节点 和 当前节点的父节点的父节点).

具体实现:

  采用链式前向星存储整棵树.整形数组newpos[i] 表示深度优先遍历序列的第 i 个点是哪个点, now 表示当前深度优先遍历序列已经有多少个点了. bool形数组visit[]用于深度优先遍历的判重,整形pre[i]表示点 i 的父节点编号,  bool型数组s[i]如果为 true, 表示第 i 个点被覆盖, bool型数组set[i]如果为 true,表示点 i 属于要求的点的集合.

代码:

 1 #include <bits/stdc++.h>
 2
 3 using namespace std;
 4 const int maxn = 1000;
 5 int pre[maxn];//存储父节点
 6 bool visit[maxn];//DFS标记数组
 7 int newpos[maxn];//遍历序列
 8 int now;
 9 int n, m;
10
11 int head[maxn];//链式前向星
12 struct Node {int to; int next;};
13 Node edge[maxn];
14
15 void DFS(int x) {
16     newpos[now ++] = x;//记录遍历序列
17     for(int k = head[x]; k != -1; k = edge[k].next) {
18         if(!visit[ edge[k].to ]) {
19             visit[ edge[k].to ] = true;
20             pre[edge[k].to] = x;//记录父节点
21             DFS(edge[k].to);
22         }
23     }
24 }
25
26 int MDS() {
27     bool s[maxn] = {0};
28     bool set[maxn] = {0};
29     int ans = 0;
30     for(int i = n - 1; i >= 0; i--) {//逆序进行贪心
31         int t = newpos[i];
32         if(!s[t]) { //如果当前点没被覆盖
33             if(! set[ pre[t] ]) {//当前点的父节点不属于支配集
34                 set[ pre[t] ] = true;//当前点的父节点加入支配集
35                 ans ++;  //支配集节点个数加 1
36             }
37             s[t] = true; //标记当前点已被覆盖
38             s[ pre[t] ] = true;// 标记当前点的父节点被覆盖
39             s[ pre[ pre[t] ] ] = true;//标记当前点的父节点的父节点被覆盖
40         }
41     }
42     return ans;
43 }
44
45 int main() {
46     /* read Graph message*/ //建图
47     memset(visit, false, sizeof(visit));//初始化
48     now = 0;
49     visit[1] = true;
50     pre[1] = 1;
51     DFS(1);//从根节点开始寻摘遍历序列
52     MDS();
53     return 0;
54 }

 (2)最小点覆盖

贪心策略:同样需要按照反方向的深度优先遍历序列来进行贪心.每检查一个结点,如果当前点和当前点的父节点都不属于顶点覆盖集合,则将父节点加入到顶点覆盖集合,并标记当前节点和其父节点都被覆盖.注意此贪心策略不适用于根节点,所以要把根节点排除在外.

具体实现:实现上基本和求最小支配集差不多,仅仅需要改动贪心部分即可.

代码:

 1 #include <bits/stdc++.h>
 2
 3 using namespace std;
 4 const int maxn = 1000;
 5 int pre[maxn];//存储父节点
 6 bool visit[maxn];//DFS标记数组
 7 int newpos[maxn];//遍历序列
 8 int now;
 9 int n, m;
10
11 int head[maxn];//链式前向星
12 struct Node {int to; int next;};
13 Node edge[maxn];
14
15 void DFS(int x) {
16     newpos[now ++] = x;//记录遍历序列
17     for(int k = head[x]; k != -1; k = edge[k].next) {
18         if(!visit[ edge[k].to ]) {
19             visit[ edge[k].to ] = true;
20             pre[edge[k].to] = x;//记录父节点
21             DFS(edge[k].to);
22         }
23     }
24 }
25
26 int MDS() {
27     bool s[maxn] = {0};
28     bool set[maxn] = {0};
29     int ans = 0;
30     for(int i = n - 1; i >= 1; i--) {//逆序进行贪心,排除掉其根节点
31         int t = newpos[i];
32         if(!s[t] && !s[ pre[t] ]) {//如果当前节点和其父节点都不属于顶点覆盖集合
33             set[ pre[t] ] = true;//把其父节点加入到顶点覆盖集合
34             ans ++; //集合内顶点个数加 1
35             s[t] = true;//标记当前节点被覆盖
36             s[ pre[t] ] = true;//标记其父节点被覆盖
37         }
38     }
39     return ans;
40 }
41
42 int main() {
43     /* read Graph message*/ //建图
44     memset(visit, false, sizeof(visit));//初始化
45     now = 0;
46     visit[1] = true;
47     pre[1] = 1;
48     DFS(1);//从第一个根节点开始寻找遍历序列
49     MDS();
50     return 0;
51 }

(3)最大独立集

贪心策略:同样和以上两个贪心问题的贪心方法差不多,需要反向遍历DFS的遍历序列,检查每一个点,如果当前节点没有被覆盖,则将当前节点加入独立集,并标记当前点和其父节点都被覆盖.

代码:

 1 #include <bits/stdc++.h>
 2
 3 using namespace std;
 4 const int maxn = 1000;
 5 int pre[maxn];//存储父节点
 6 bool visit[maxn];//DFS标记数组
 7 int newpos[maxn];//遍历序列
 8 int now;
 9 int n, m;
10
11 int head[maxn];//链式前向星
12 struct Node {int to; int next;};
13 Node edge[maxn];
14
15 void DFS(int x) {
16     newpos[now ++] = x;//记录遍历序列
17     for(int k = head[x]; k != -1; k = edge[k].next) {
18         if(!visit[ edge[k].to ]) {
19             visit[ edge[k].to ] = true;
20             pre[edge[k].to] = x;//记录父节点
21             DFS(edge[k].to);
22         }
23     }
24 }
25
26 int MDS() {
27     bool s[maxn] = {0};
28     bool set[maxn] = {0};
29     int ans = 0;
30     for(int i = n - 1; i >= 0; i--) {//按照DFS遍历序列的逆序进行贪心
31         int t = newpos[i];
32         if(!s[t]) {//如果当前节点没有被覆盖
33             set[t] = true;//把当前节点加入到独立集
34             ans ++;//独立集中点的个数加 1
35             s[t] = true;//标记当前点已经被覆盖
36             s[ pre[t] ] = true;//标记当前点的父节点已经被覆盖
37         }
38     }
39     return ans;
40 }
41
42 int main() {
43     /* read Graph message*/ //建图
44     memset(visit, false, sizeof(visit));//初始化
45     now = 0;
46     visit[1] = true;
47     pre[1] = 1;
48     DFS(1);//从第一个根节点开始寻找遍历序列
49     MDS();
50     return 0;
51 }
时间: 2025-01-18 08:08:37

贪心法求树的最小支配集,最小点覆盖,最大独立集的相关文章

树的最小支配集 最小点覆盖 与 最大独立集 (图论)

做 战略游戏 这道题的时候看到了这个东西,于是就来这里写了一下. 首先看一下三者的定义: 定义1 对于图G=(V,E)来说,最小支配集指的是从V中取尽量少的点组成一个集合,使得对于V中剩余的点都与取出来的点有边相连.也就是说,设V‘是图G的一个支配集,则对于图中的任意一个顶点u,要么属于集合V’,要么与V‘中的顶点相邻.在V’中出去任何元素后V‘不再是支配集,则支配集是极小支配集.称G的所有支配集中顶点个数最少的支配集为最小支配集,最小支配集中顶点的个数称为支配数. 定义2 对于图G=(V,E)

树的最小支配集,最小点覆盖,最大独立集两种算法

1.基本概念 对图G=<V,E>, 最小支配集:从V中取尽量少的点组成一个集合,使得V中剩余的点都与取出来的点有边相连 最小点覆盖:从V中取尽量少的点组成一个集合,使得E中所有边都与取出来的点相连 最大独立集:从V中取尽量多的点组成一个集合,使得这些点之间没有边相连 2.贪心法求树的最小支配集,最小点覆盖,最大独立集模板 基本算法: 以最小支配集为例,首先选择一点为根,按照深度优先遍历得到遍历序列,按照所得序列的反向序列的顺序进行贪心,对于一个既不属于支配集也不与支配集中的点相连的点来说,如果

【POJ3659】【USACO 2008 Jan Gold】 3.Cell Phone Network 树上最小支配集/贪心 两种做法

题意:求树上最小支配集 最小支配集:点集,即每个点可以"支配"到相邻点,求最少点数可以使所有点被支配. 图上的最小支配集是NP的,但是树上的可以DP做,是O(n)的. 暴力做就好了, f[i]表示此 点被选时的子树全支配的最小代价 g[i]表示其父亲节 点被选时的子树全支配的最小代价 h[i]表示其某子节 点被选时的子树全支配的最小代价 然后暴力转移. (v是子节点) f[x]=∑(min(f[v],min(g[v],h[v])))+1; g[x]=∑(min(f[v],h[v]));

POJ 3659 Cell Phone Network(树的最小支配集)(贪心)

Cell Phone Network Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6781   Accepted: 2429 Description Farmer John has decided to give each of his cows a cell phone in hopes to encourage their social interaction. This, however, requires hi

POJ3659 Cell Phone Network【最小支配集】【贪心】

Cell Phone Network Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 5735Accepted: 2053 Description Farmer John has decided to give each of his cows a cell phone in hopes to encourage their social interaction. This, however, requires him to

树的最小支配集、最小点覆盖、最大独立集【模板】

最小支配集:指从所有顶点中取尽量少的点组成一个集合,使得剩下的所有点都与取出来的点有边相连.顶点个数最小的支配集被称为最小支配集.这里用贪心法来求. 1.以1号点深度优先搜索整棵树,求出每个点在DFS中的编号和每个点的父亲节点编号. 2.按DFS的反向序列检查,如果当前点既不属于支配集也不与支配集中的点相连,且它的父亲也不属于支配集,将其父亲点加入支配集,支配集个数加1. 3.标记当前结点.当前结点的父节点(属于支配集).当前结点的父节点的父节点(与支配集中的点相连). #include<ios

POJ3659 Cell Phone Network(树上最小支配集:树型DP)

题目求一棵树的最小支配数. 支配集,即把图的点分成两个集合,所有非支配集内的点都和支配集内的某一点相邻. 听说即使是二分图,最小支配集的求解也是还没多项式算法的.而树上求最小支配集树型DP就OK了. 树上的每个结点作为其子树的根可以有三个状态: 不属于支配集且还没被支配 不属于支配集但被其孩子支配 属于支配集 那么就是用dp[u][1\2\3]来表示动归的状态. 123转移该怎么转移就怎么转移..最后的结果就是min(dp[root][2],dp[root][3]). 要注意的是对于有些结点前2

树的问题小结(最小生成树、次小生成树、最小树形图、LCA、最小支配集、最小点覆盖、最大独立集)

树的定义:连通无回路的无向图是一棵树. 有关树的问题: 1.最小生成树. 2.次小生成树. 3.有向图的最小树形图. 4.LCA(树上两点的最近公共祖先). 5.树的最小支配集.最小点覆盖.最大独立集. 一.最小生成树 解决的问题是:求无向图中边权值之和最小的生成树. 算法有Kruskal和Prim. Kruskal使用前向星和并查集实现,可以存储重边(平行边),时间复杂度是O(m log m  +  m),m是边的数量. Prim使用邻接矩阵建图,不可以存储重边(平行边),如果出现重边,存储的

树形DP 树的最小支配集,最小点覆盖与最大独立集

最小支配集: 从V中选取尽量少的点组成一个集合,让V中剩余的点都与取出来的点有边相连. (点) 最小点覆盖: 从V中选取尽量少的点组成一个集合V1,让所有边(u,v)中要么u属于V1,要么v属于V1 (边) 最大独立集: 从V中选取尽量多的点组成一个集合,让这些点中间没有边项链,也就是说对于任何一条边,u,v不能同时属于集合V1. 1.贪心算法 首先选取一个点为根节点,求出所有节点对应的DFS序列,按照所得序列反向进行贪心,这样保证对于每个点来说,当子树都被处理过之后才会处理该节点 int p[