最小k度限制生成树

【题目描述】

给你一个图,n个点,m条边,求一颗生成树满足如下条件:

(1)结点1的度不超过k。

(2)在(1)条件下所求生成树最小。

【算法引入】

最小k度限制生成树,就是指有特殊的某一点的度不能超过k时的最小生成树。

如果T是G的一个生成树且dT(v0)=k,则称T为G的k度限制生成树。

G中权值和最小的k度限制生成树称为G的最小k度生成树。

【算法思想】

设特殊的那点为v0,先把v0删除,求出剩下连通图的所有最小生成树。

假如有m棵最小生成树,那么这些生成树必定要跟v0点相连。

也就是说这棵生成树的v0点至少是m度的。

若m>k,条件不成立,无法找到最小k度限制生成树。

若m<=k,则枚举m到k的所有最小生成树,即一步步将v0点的度加1,直到v0点的度为k为止。

则v0点度从m到k的(k-m+1)棵最小生成树中最小的那棵即为答案。

【算法步骤】

(1) 原图中去掉和V0相连的所有边(可以先存两个图,建议一个邻接矩阵,一个边表,用方便枚举边的邻接表来构造新图)。

得到m个连通分量,则这m个连通分量必须通过v0来连接。

则在图G的所有生成树中dT(v0)>=m。

则当k<m时,问题无解。

对每个连通分量求一次最小生成树。

每个连通分量的的最小生成树可以直接用一个循环,循环着Kruskal求出。

这里利用了联通分量间的独立性,对每个连通分量分别求最小生成树,和放在一起求,毫不影响。

而且kruskral算法保证了各连通分量边的有序性。

 1 int find(int x)  {return f[x]==x?x:f[x]=find(f[x]);}//路径压缩
 2 bool cmp(node a,node b)  {return a.v<b.v;}
 3 void Kruskal()
 4 {
 5     for(int i=1;i<=n;i++)  f[i]=i;//并查集初始化
 6     sort(e+1,e+m+1,cmp);//按边权排序
 7     for(int i=1;i<=m;i++)
 8     {
 9         if(e[i].x==1||e[i].y==1)  continue;
10         int x=find(e[i].x),y=find(e[i].y);
11         if(x!=y)
12         {
13             f[x]=y;
14             check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=1;//check表示有边相连
15             ans+=e[i].v;
16         }
17     }
18 }

(2) 对于每个连通分量V’,用一条与V0直接连接的最小的边把它与V0点连接起来,使其整体成为一个生成树。

就得到了一个m度限制生成树,即为最小m度限制生成树。

 1 void solve1()//计算最小m度生成树
 2 {
 3     for(int i=1;i<=n;i++)  Min[i]=INF;
 4     for(int i=2;i<=n;i++)//找出从1点连到每个连通块的最小边权
 5         if(a[1][i]!=-1)
 6         {
 7             int t=find(i);
 8             if(a[i][1]<Min[t])
 9             {
10                 Min[t]=a[i][1];
11                 temp[t]=i;
12             }
13         }
14     for(int i=1;i<=n;i++)//把1点和每个连通块连接起来,计算答案
15         if(Min[i]!=INF)
16         {
17             md++;
18             check[1][temp[i]]=check[temp[i]][1]=1;
19             ans+=a[1][temp[i]];
20         }
21 }

(3)由最小m度限制生成树得到最小m+1度限制生成树。

连接和V0相邻的点v,则可以知道一定会有一个环出现(因为原来是一个生成树);

只要找到这个环上的最大权边(不能与v0点直接相连)并删除,就可以得到一个m+1度限制生成树;

枚举所有和V0相邻点v,找到替换后,增加权值最小的一次替换(如果找不到这样的边,就说明已经求出);

就可以求得m+1度限制生成树;

如果每添加一条边,都需要对环上的边一一枚举,时间复杂度将比较高;

用动态规划解决;

设dp(v)为路径v0—v上与v0无关联且权值最大的边;

定义father(v)为v的父结点,由此可以得到状态转移方程:

dp(v)=max(dp(father(v)),ω(father(v),v));

边界条件为dp[v0]=-∞(因为每次寻找的是最大边,所以-∞不会被考虑),dp[v’]=-∞|(v0,v’)∈E(T);

当dT(v0)=k时停止(即当V0的度为k的时候停止),但不一定k的时候最优;

 1 void dfs(int x,int fa)//动规计算dp,dp记录的是从1到某点路径中权值最大的边,且此边不与1相连
 2 {
 3     for(int i=2;i<=n;i++)//枚举点
 4         if(check[x][i]&&i!=fa)//有边相连
 5         {
 6             if(dp[i].v==-1)//没被搜过,更新dp
 7             {
 8                 if(a[x][i]<dp[x].v)  dp[i]=dp[x];
 9                 else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
10             }
11             dfs(i,x);
12         }
13 }
14 void solve2()//计算最小k度生成树
15 {
16     for(int i=md+1;i<=k;i++)
17     {
18         memset(dp,-1,sizeof(dp));  dp[1].v=-INF;
19         for(int j=2;j<=n;j++)  if(check[1][j])  e[j].v=-INF;//把与1相连的边设为-oo,避免dfs时搜到
20         dfs(1,0);  int t=0,minn=INF;
21         for(int j=2;j<=n;j++)
22             if(a[1][j]!=-1&&a[1][j]-dp[j].v<minn)//记录对答案贡献最大的边
23             {
24                 minn=a[1][j]-dp[j].v;
25                 t=j;
26             }
27         if(minn>=0)  break;//找不到就说明已经增广完了
28         int x=dp[t].x,y=dp[t].y;
29         check[1][t]=check[t][1]=1;//维护图的性质
30         check[x][y]=check[y][x]=0;
31         ans+=minn;
32     }
33 }

【算法实现】

完整代码:

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<cstdlib>
  5 #include<cmath>
  6 #include<ctime>
  7 #include<algorithm>
  8 using namespace std;
  9 #define INF 1000000000
 10 #define MAXN 105
 11 struct node{int x,y,v;}e[MAXN],dp[MAXN];//e是图的边表
 12 int n,m,k,ans,md,f[MAXN],Min[MAXN],temp[MAXN],a[MAXN][MAXN],check[MAXN][MAXN];
 13 inline int read()
 14 {
 15     int x=0,f=1;  char ch=getchar();
 16     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
 17     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
 18     return x*f;
 19 }
 20 void init()
 21 {
 22     n=read();  m=read();  k=read();//n是结点数,m是边数,k是度数限制
 23     memset(a,-1,sizeof(a));//a是图的邻接矩阵
 24     for(int i=1;i<=m;i++)
 25     {
 26         int x=read(),y=read(),v=read();
 27         e[i].x=x;  e[i].y=y;  e[i].v=v;
 28         if(a[x][y]==-1)  a[x][y]=a[y][x]=v;
 29         else a[x][y]=a[y][x]=min(a[x][y],v);  //判重边
 30     }
 31 }
 32 int find(int x)  {return f[x]==x?x:f[x]=find(f[x]);}//路径压缩
 33 bool cmp(node a,node b)  {return a.v<b.v;}
 34 void Kruskal()
 35 {
 36     for(int i=1;i<=n;i++)  f[i]=i;//并查集初始化
 37     sort(e+1,e+m+1,cmp);//按边权排序
 38     for(int i=1;i<=m;i++)
 39     {
 40         if(e[i].x==1||e[i].y==1)  continue;
 41         int x=find(e[i].x),y=find(e[i].y);
 42         if(x!=y)
 43         {
 44             f[x]=y;
 45             check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=1;//check表示有边相连
 46             ans+=e[i].v;
 47         }
 48     }
 49 }
 50 void solve1()//计算最小m度生成树
 51 {
 52     for(int i=1;i<=n;i++)  Min[i]=INF;
 53     for(int i=2;i<=n;i++)//找出从1点连到每个连通块的最小边权
 54         if(a[1][i]!=-1)
 55         {
 56             int t=find(i);
 57             if(a[i][1]<Min[t])
 58             {
 59                 Min[t]=a[i][1];
 60                 temp[t]=i;
 61             }
 62         }
 63     for(int i=1;i<=n;i++)//把1点和每个连通块连接起来,计算答案
 64         if(Min[i]!=INF)
 65         {
 66             md++;
 67             check[1][temp[i]]=check[temp[i]][1]=1;
 68             ans+=a[1][temp[i]];
 69         }
 70 }
 71 void dfs(int x,int fa)//动规计算dp,dp记录的是从1到某点路径中权值最大的边,且此边不与1相连
 72 {
 73     for(int i=2;i<=n;i++)//枚举点
 74         if(check[x][i]&&i!=fa)//有边相连
 75         {
 76             if(dp[i].v==-1)//没被搜过,更新dp
 77             {
 78                 if(a[x][i]<dp[x].v)  dp[i]=dp[x];
 79                 else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
 80             }
 81             dfs(i,x);
 82         }
 83 }
 84 void solve2()//计算最小k度生成树
 85 {
 86     for(int i=md+1;i<=k;i++)
 87     {
 88         memset(dp,-1,sizeof(dp));  dp[1].v=-INF;
 89         for(int j=2;j<=n;j++)  if(check[1][j])  e[j].v=-INF;//把与1相连的边设为-oo,避免dfs时搜到
 90         dfs(1,0);  int t=0,minn=INF;
 91         for(int j=2;j<=n;j++)
 92             if(a[1][j]!=-1&&a[1][j]-dp[j].v<minn)//记录对答案贡献最大的边
 93             {
 94                 minn=a[1][j]-dp[j].v;
 95                 t=j;
 96             }
 97         if(minn>=0)  break;//找不到就说明已经增广完了
 98         int x=dp[t].x,y=dp[t].y;
 99         check[1][t]=check[t][1]=1;//维护图的性质
100         check[x][y]=check[y][x]=0;
101         ans+=minn;
102     }
103 }
104 int main()
105 {
106     freopen("cin.in","r",stdin);
107     freopen("cout.out","w",stdout);
108     init();
109     Kruskal();
110     solve1();
111     solve2();
112     printf("%d\n",ans);
113     return 0;
114 }

【例题一】poj 1639  

题目大意:

给出m条边,每条边有两个端点和一个权值,求这个图在满足以下条件的情况下的最小生成树:

在所有点中,有一个特殊点Park,它在求得的最小生成树中的度必须小于等于某个值。

 

这题需要注意在输入时处理字符串,把Park设为根结点,然后用上述算法即可。

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<cstdlib>
  5 #include<cmath>
  6 #include<ctime>
  7 #include<algorithm>
  8 #include<string>
  9 #include<map>
 10 using namespace std;
 11 #define INF 1000000000
 12 #define MAXN 105
 13 struct node{int x,y,v;}e[MAXN*MAXN],dp[MAXN];
 14 int n(1),m,K,oo,ans,md,Min[MAXN],temp[MAXN],f[MAXN],a[MAXN][MAXN],check[MAXN][MAXN];
 15 string s;
 16 map<string,int> Map; 
 17 inline int read()
 18 {
 19     int x=0,f=1;  char ch=getchar();
 20     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
 21     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
 22     return x*f;
 23 }
 24 bool cmp(node a,node b)  {return a.v<b.v;}
 25 int cal()  {if(Map.find(s)==Map.end())  Map[s]=++n;  return Map[s];}
 26 int find(int x)  {return f[x]==x?x:f[x]=find(f[x]);}
 27 void init()
 28 {
 29     m=read();  Map["Park"]=1;  
 30     memset(a,-1,sizeof(a));  
 31     memset(Min,10,sizeof(Min));
 32     oo=Min[0];
 33     for(int i=1;i<=m;i++)
 34     {
 35         cin>>s;  e[i].x=cal();
 36         cin>>s;  e[i].y=cal();
 37         e[i].v=read();  
 38         if(a[e[i].x][e[i].y]==-1)  a[e[i].y][e[i].x]=a[e[i].x][e[i].y]=e[i].v;
 39         else a[e[i].x][e[i].y]=a[e[i].y][e[i].x]=min(a[e[i].x][e[i].y],e[i].v);
 40     }
 41     K=read();
 42 }
 43 void kruskal()
 44 {
 45     for(int i=1;i<=n;i++)  f[i]=i;
 46     sort(e+1,e+m+1,cmp);
 47     for(int i=1;i<=m;i++)
 48     {
 49         if(e[i].x==1||e[i].y==1)  continue;
 50         int x=find(e[i].x),y=find(e[i].y);
 51         if(x==y)  continue;
 52         check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=1;
 53         f[y]=x;
 54         ans+=e[i].v;
 55     }
 56 }
 57 void solve1()
 58 {
 59     for(int i=2;i<=n;i++)
 60         if(a[i][1]!=-1)
 61         {
 62             int t=find(i);
 63             if(a[i][1]<Min[t])
 64             {
 65                 temp[t]=i;
 66                 Min[t]=a[i][1];
 67             }
 68         }
 69     for(int i=1;i<=n;i++)
 70         if(Min[i]!=oo)
 71         {
 72             md++;
 73             check[1][temp[i]]=check[temp[i]][1]=1;
 74             ans+=a[1][temp[i]];
 75         }    
 76 }
 77 void dfs(int x,int fa)
 78 {
 79     for(int i=2;i<=n;i++)
 80         if(check[x][i]&&i!=fa)
 81         {
 82             if(dp[i].v==-1)
 83             {
 84                 if(a[x][i]<dp[x].v)  dp[i]=dp[x];
 85                 else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
 86             }
 87             dfs(i,x);
 88         }
 89 }
 90 void solve2()
 91 {
 92     for(int i=md+1;i<=K;i++)
 93     {
 94         memset(dp,-1,sizeof(dp));  dp[1].v=-INF;
 95         for(int j=2;j<=n;j++)  if(check[1][j])  dp[j].v=-INF;
 96         dfs(1,-1);  int t=0,minn=INF;
 97         for(int j=2;j<=n;j++)  
 98             if(a[1][j]!=-1&&a[1][j]-dp[j].v<minn)  
 99             {
100                 minn=a[1][j]-dp[j].v; 
101                 t=j;
102             }
103         if(minn>=0)  break;
104         check[1][t]=check[t][1]=1;
105         int x=dp[t].x,y=dp[t].y;
106         check[x][y]=check[y][x]=0;
107         ans+=minn;
108     }
109 }
110 int main()
111 {
112     init();
113     kruskal();
114     solve1();
115     solve2();
116     printf("Total miles driven: %d\n",ans);
117     return 0;
118 }

 【例题二】poj 2349

题目大意:

某地区共有n座村庄,每座村庄的坐标用一对整数(x, y)表示,现在要在村庄之间建立通讯网络。通讯工具有两种,分别是需要铺设的普通线路和卫星设备。卫星设备数量有限,只能给k个村庄配备卫星设备。拥有卫星设

备的村庄互相间直接通讯;铺设了线路的村庄之间也可以通讯。卫星分配是不受限制的。

输入:

第一行:数据组数CASE

接下来每组数据第一行:k,n,分别为卫星数和村庄数。

接下来n行,每行2个数,x,y,表示村庄的坐标。

输出:

最短通讯网络中最长的路。

 

题解详见:2004年国家集训队论文——汪汀《最小生成树问题的扩展》

附参考代码:

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<cstdlib>
  5 #include<cmath>
  6 #include<ctime>
  7 #include<algorithm>
  8 using namespace std;
  9 #define INF 1000000000
 10 #define MAXN 505
 11 struct node{int x,y;double v;}e[MAXN*MAXN],dp[MAXN*MAXN];
 12 int n,m,k,md,X[MAXN],Y[MAXN],f[MAXN],temp[MAXN],check[MAXN][MAXN];
 13 double ans,Min[MAXN],a[MAXN][MAXN];
 14 inline int read()
 15 {
 16     int x=0,f=1;  char ch=getchar();
 17     while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
 18     while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
 19     return x*f;
 20 }
 21 double cal(int a,int b)  {return sqrt(((X[a]-X[b])*(X[a]-X[b])+(Y[a]-Y[b])*(Y[a]-Y[b]))*1.00);}
 22 void insert(int x,int y,double v)
 23 {
 24     e[++m].x=x;e[m].y=y;e[m].v=v;
 25     if(a[x][y]==-1)  a[x][y]=a[y][x]=v;
 26     else a[x][y]=a[y][x]=(a[x][y]<v?v:a[x][y]);
 27 }
 28 void init()
 29 {
 30     memset(a,0,sizeof(a));
 31     k=read();  n=read();
 32     for(int i=1;i<=n;i++)
 33     {
 34         X[i]=read(),Y[i]=read();
 35         for(int j=1;j<i;j++)  insert(i+1,j+1,cal(i,j));
 36     }
 37     for(int i=1;i<=n;i++)  insert(1,i+1,0);
 38     n++;
 39 }
 40 bool cmp(node a,node b)  {return a.v<b.v;}
 41 int find(int x)  {return f[x]==x?x:f[x]=find(f[x]);}
 42 void Kruskal()
 43 {
 44     for(int i=1;i<=n;i++)  f[i]=i;
 45     sort(e+1,e+m+1,cmp);
 46     for(int i=1;i<=m;i++)
 47     {
 48         if(e[i].x==1||e[i].y==1)  continue;
 49         int x=find(e[i].x),y=find(e[i].y);
 50         if(x==y)  continue;
 51         f[x]=y;
 52         check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=1;
 53     }
 54 }
 55 void solve1()
 56 {
 57     for(int i=1;i<=n;i++)  Min[i]=INF;
 58     for(int i=2;i<=n;i++)
 59         if(a[1][i]!=-1)
 60         {
 61             int t=find(i);
 62             if(a[i][1]<Min[t])
 63             {
 64                 Min[t]=a[i][1];
 65                 temp[t]=i;
 66             }
 67         }
 68     for(int i=1;i<=n;i++)
 69         if(Min[i]!=INF)
 70         {
 71             md++;
 72             check[1][temp[i]]=check[temp[i]][1]=1;
 73         }
 74 }
 75 void dfs(int x,int fa)
 76 {
 77     for(int i=2;i<=n;i++)
 78         if(check[x][i]&&i!=fa)
 79         {
 80             if(dp[i].v==-1)
 81             {
 82                 if(a[x][i]<dp[x].v)  dp[i]=dp[x];
 83                 else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
 84             }
 85             dfs(i,x);
 86         }
 87 }
 88 void solve2()
 89 {
 90     for(int i=md+1;i<=k;i++)
 91     {
 92         for(int i=0;i<=n;i++)  dp[i].v=-1;    dp[1].v=-INF;
 93         for(int j=2;j<=n;j++)  if(check[1][j])  e[j].v=-INF;
 94         dfs(1,0);  int t=0;  double minn=INF;
 95         for(int j=2;j<=n;j++)
 96             if(a[1][j]!=-1&&a[1][j]-dp[j].v<minn)
 97             {
 98                 minn=a[1][j]-dp[j].v;
 99                 t=j;
100             }
101         if(minn>=0)  break;
102         int x=dp[t].x,y=dp[t].y;
103         check[1][t]=check[t][1]=1;
104         check[x][y]=check[y][x]=0;
105     }
106 }
107 void pre()
108 {
109     n=m=k=md=0;  ans=0;
110     memset(check,0,sizeof(check));
111 }
112 int main()
113 {
114     //freopen("cin.in","r",stdin);
115     //freopen("cout.out","w",stdout);
116     int CASE=read();
117     while(CASE--)
118     {
119         pre();
120         init();
121         Kruskal();
122         solve1();
123         solve2();
124         for(int i=1;i<=n;i++)
125             for(int j=1;j<=n;j++)
126                 if(check[i][j]&&a[i][j]>ans)  ans=a[i][j];
127         printf("%.2lf\n",ans);    
128     }
129     return 0;
130 }

 

posted @ 2016-10-06 20:07  chty  阅读(1913)  评论(0编辑  收藏  举报