【题目描述】
给你一个图,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 ",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 ",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 ",ans); 128 } 129 return 0; 130 }