首页 > 代码库 > 最小k度限制生成树

最小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 }

 

最小k度限制生成树