最小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算法保证了各连通分量边的有序性。
- int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}//路径压缩
- bool cmp(node a,node b) {return a.v<b.v;}
- void Kruskal()
- {
- for(int i=;i<=n;i++) f[i]=i;//并查集初始化
- sort(e+,e+m+,cmp);//按边权排序
- for(int i=;i<=m;i++)
- {
- if(e[i].x==||e[i].y==) continue;
- int x=find(e[i].x),y=find(e[i].y);
- if(x!=y)
- {
- f[x]=y;
- check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=;//check表示有边相连
- ans+=e[i].v;
- }
- }
- }
(2) 对于每个连通分量V’,用一条与V0直接连接的最小的边把它与V0点连接起来,使其整体成为一个生成树。
就得到了一个m度限制生成树,即为最小m度限制生成树。
- void solve1()//计算最小m度生成树
- {
- for(int i=;i<=n;i++) Min[i]=INF;
- for(int i=;i<=n;i++)//找出从1点连到每个连通块的最小边权
- if(a[][i]!=-)
- {
- int t=find(i);
- if(a[i][]<Min[t])
- {
- Min[t]=a[i][];
- temp[t]=i;
- }
- }
- for(int i=;i<=n;i++)//把1点和每个连通块连接起来,计算答案
- if(Min[i]!=INF)
- {
- md++;
- check[][temp[i]]=check[temp[i]][]=;
- ans+=a[][temp[i]];
- }
- }
(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的时候最优;
- void dfs(int x,int fa)//动规计算dp,dp记录的是从1到某点路径中权值最大的边,且此边不与1相连
- {
- for(int i=;i<=n;i++)//枚举点
- if(check[x][i]&&i!=fa)//有边相连
- {
- if(dp[i].v==-)//没被搜过,更新dp
- {
- if(a[x][i]<dp[x].v) dp[i]=dp[x];
- else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
- }
- dfs(i,x);
- }
- }
- void solve2()//计算最小k度生成树
- {
- for(int i=md+;i<=k;i++)
- {
- memset(dp,-,sizeof(dp)); dp[].v=-INF;
- for(int j=;j<=n;j++) if(check[][j]) e[j].v=-INF;//把与1相连的边设为-oo,避免dfs时搜到
- dfs(,); int t=,minn=INF;
- for(int j=;j<=n;j++)
- if(a[][j]!=-&&a[][j]-dp[j].v<minn)//记录对答案贡献最大的边
- {
- minn=a[][j]-dp[j].v;
- t=j;
- }
- if(minn>=) break;//找不到就说明已经增广完了
- int x=dp[t].x,y=dp[t].y;
- check[][t]=check[t][]=;//维护图的性质
- check[x][y]=check[y][x]=;
- ans+=minn;
- }
- }
【算法实现】
完整代码:
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- #include<cstdlib>
- #include<cmath>
- #include<ctime>
- #include<algorithm>
- using namespace std;
- #define INF 1000000000
- #define MAXN 105
- struct node{int x,y,v;}e[MAXN],dp[MAXN];//e是图的边表
- int n,m,k,ans,md,f[MAXN],Min[MAXN],temp[MAXN],a[MAXN][MAXN],check[MAXN][MAXN];
- inline int read()
- {
- int x=,f=; char ch=getchar();
- while(!isdigit(ch)) {if(ch=='-') f=-; ch=getchar();}
- while(isdigit(ch)) {x=x*+ch-''; ch=getchar();}
- return x*f;
- }
- void init()
- {
- n=read(); m=read(); k=read();//n是结点数,m是边数,k是度数限制
- memset(a,-,sizeof(a));//a是图的邻接矩阵
- for(int i=;i<=m;i++)
- {
- int x=read(),y=read(),v=read();
- e[i].x=x; e[i].y=y; e[i].v=v;
- if(a[x][y]==-) a[x][y]=a[y][x]=v;
- else a[x][y]=a[y][x]=min(a[x][y],v); //判重边
- }
- }
- int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}//路径压缩
- bool cmp(node a,node b) {return a.v<b.v;}
- void Kruskal()
- {
- for(int i=;i<=n;i++) f[i]=i;//并查集初始化
- sort(e+,e+m+,cmp);//按边权排序
- for(int i=;i<=m;i++)
- {
- if(e[i].x==||e[i].y==) continue;
- int x=find(e[i].x),y=find(e[i].y);
- if(x!=y)
- {
- f[x]=y;
- check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=;//check表示有边相连
- ans+=e[i].v;
- }
- }
- }
- void solve1()//计算最小m度生成树
- {
- for(int i=;i<=n;i++) Min[i]=INF;
- for(int i=;i<=n;i++)//找出从1点连到每个连通块的最小边权
- if(a[][i]!=-)
- {
- int t=find(i);
- if(a[i][]<Min[t])
- {
- Min[t]=a[i][];
- temp[t]=i;
- }
- }
- for(int i=;i<=n;i++)//把1点和每个连通块连接起来,计算答案
- if(Min[i]!=INF)
- {
- md++;
- check[][temp[i]]=check[temp[i]][]=;
- ans+=a[][temp[i]];
- }
- }
- void dfs(int x,int fa)//动规计算dp,dp记录的是从1到某点路径中权值最大的边,且此边不与1相连
- {
- for(int i=;i<=n;i++)//枚举点
- if(check[x][i]&&i!=fa)//有边相连
- {
- if(dp[i].v==-)//没被搜过,更新dp
- {
- if(a[x][i]<dp[x].v) dp[i]=dp[x];
- else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
- }
- dfs(i,x);
- }
- }
- void solve2()//计算最小k度生成树
- {
- for(int i=md+;i<=k;i++)
- {
- memset(dp,-,sizeof(dp)); dp[].v=-INF;
- for(int j=;j<=n;j++) if(check[][j]) e[j].v=-INF;//把与1相连的边设为-oo,避免dfs时搜到
- dfs(,); int t=,minn=INF;
- for(int j=;j<=n;j++)
- if(a[][j]!=-&&a[][j]-dp[j].v<minn)//记录对答案贡献最大的边
- {
- minn=a[][j]-dp[j].v;
- t=j;
- }
- if(minn>=) break;//找不到就说明已经增广完了
- int x=dp[t].x,y=dp[t].y;
- check[][t]=check[t][]=;//维护图的性质
- check[x][y]=check[y][x]=;
- ans+=minn;
- }
- }
- int main()
- {
- freopen("cin.in","r",stdin);
- freopen("cout.out","w",stdout);
- init();
- Kruskal();
- solve1();
- solve2();
- printf("%d\n",ans);
- return ;
- }
【例题一】poj 1639
题目大意:
给出m条边,每条边有两个端点和一个权值,求这个图在满足以下条件的情况下的最小生成树:
在所有点中,有一个特殊点Park,它在求得的最小生成树中的度必须小于等于某个值。
这题需要注意在输入时处理字符串,把Park设为根结点,然后用上述算法即可。
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- #include<cstdlib>
- #include<cmath>
- #include<ctime>
- #include<algorithm>
- #include<string>
- #include<map>
- using namespace std;
- #define INF 1000000000
- #define MAXN 105
- struct node{int x,y,v;}e[MAXN*MAXN],dp[MAXN];
- int n(),m,K,oo,ans,md,Min[MAXN],temp[MAXN],f[MAXN],a[MAXN][MAXN],check[MAXN][MAXN];
- string s;
- map<string,int> Map;
- inline int read()
- {
- int x=,f=; char ch=getchar();
- while(!isdigit(ch)) {if(ch=='-') f=-; ch=getchar();}
- while(isdigit(ch)) {x=x*+ch-''; ch=getchar();}
- return x*f;
- }
- bool cmp(node a,node b) {return a.v<b.v;}
- int cal() {if(Map.find(s)==Map.end()) Map[s]=++n; return Map[s];}
- int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}
- void init()
- {
- m=read(); Map["Park"]=;
- memset(a,-,sizeof(a));
- memset(Min,,sizeof(Min));
- oo=Min[];
- for(int i=;i<=m;i++)
- {
- cin>>s; e[i].x=cal();
- cin>>s; e[i].y=cal();
- e[i].v=read();
- if(a[e[i].x][e[i].y]==-) a[e[i].y][e[i].x]=a[e[i].x][e[i].y]=e[i].v;
- 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);
- }
- K=read();
- }
- void kruskal()
- {
- for(int i=;i<=n;i++) f[i]=i;
- sort(e+,e+m+,cmp);
- for(int i=;i<=m;i++)
- {
- if(e[i].x==||e[i].y==) continue;
- int x=find(e[i].x),y=find(e[i].y);
- if(x==y) continue;
- check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=;
- f[y]=x;
- ans+=e[i].v;
- }
- }
- void solve1()
- {
- for(int i=;i<=n;i++)
- if(a[i][]!=-)
- {
- int t=find(i);
- if(a[i][]<Min[t])
- {
- temp[t]=i;
- Min[t]=a[i][];
- }
- }
- for(int i=;i<=n;i++)
- if(Min[i]!=oo)
- {
- md++;
- check[][temp[i]]=check[temp[i]][]=;
- ans+=a[][temp[i]];
- }
- }
- void dfs(int x,int fa)
- {
- for(int i=;i<=n;i++)
- if(check[x][i]&&i!=fa)
- {
- if(dp[i].v==-)
- {
- if(a[x][i]<dp[x].v) dp[i]=dp[x];
- else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
- }
- dfs(i,x);
- }
- }
- void solve2()
- {
- for(int i=md+;i<=K;i++)
- {
- memset(dp,-,sizeof(dp)); dp[].v=-INF;
- for(int j=;j<=n;j++) if(check[][j]) dp[j].v=-INF;
- dfs(,-); int t=,minn=INF;
- for(int j=;j<=n;j++)
- if(a[][j]!=-&&a[][j]-dp[j].v<minn)
- {
- minn=a[][j]-dp[j].v;
- t=j;
- }
- if(minn>=) break;
- check[][t]=check[t][]=;
- int x=dp[t].x,y=dp[t].y;
- check[x][y]=check[y][x]=;
- ans+=minn;
- }
- }
- int main()
- {
- init();
- kruskal();
- solve1();
- solve2();
- printf("Total miles driven: %d\n",ans);
- return ;
- }
【例题二】poj 2349
题目大意:
某地区共有n座村庄,每座村庄的坐标用一对整数(x, y)表示,现在要在村庄之间建立通讯网络。通讯工具有两种,分别是需要铺设的普通线路和卫星设备。卫星设备数量有限,只能给k个村庄配备卫星设备。拥有卫星设
备的村庄互相间直接通讯;铺设了线路的村庄之间也可以通讯。卫星分配是不受限制的。
输入:
第一行:数据组数CASE
接下来每组数据第一行:k,n,分别为卫星数和村庄数。
接下来n行,每行2个数,x,y,表示村庄的坐标。
输出:
最短通讯网络中最长的路。
题解详见:2004年国家集训队论文——汪汀《最小生成树问题的扩展》
附参考代码:
- #include<iostream>
- #include<cstdio>
- #include<cstring>
- #include<cstdlib>
- #include<cmath>
- #include<ctime>
- #include<algorithm>
- using namespace std;
- #define INF 1000000000
- #define MAXN 505
- struct node{int x,y;double v;}e[MAXN*MAXN],dp[MAXN*MAXN];
- int n,m,k,md,X[MAXN],Y[MAXN],f[MAXN],temp[MAXN],check[MAXN][MAXN];
- double ans,Min[MAXN],a[MAXN][MAXN];
- inline int read()
- {
- int x=,f=; char ch=getchar();
- while(!isdigit(ch)) {if(ch=='-') f=-; ch=getchar();}
- while(isdigit(ch)) {x=x*+ch-''; ch=getchar();}
- return x*f;
- }
- 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);}
- void insert(int x,int y,double v)
- {
- e[++m].x=x;e[m].y=y;e[m].v=v;
- if(a[x][y]==-) a[x][y]=a[y][x]=v;
- else a[x][y]=a[y][x]=(a[x][y]<v?v:a[x][y]);
- }
- void init()
- {
- memset(a,,sizeof(a));
- k=read(); n=read();
- for(int i=;i<=n;i++)
- {
- X[i]=read(),Y[i]=read();
- for(int j=;j<i;j++) insert(i+,j+,cal(i,j));
- }
- for(int i=;i<=n;i++) insert(,i+,);
- n++;
- }
- bool cmp(node a,node b) {return a.v<b.v;}
- int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}
- void Kruskal()
- {
- for(int i=;i<=n;i++) f[i]=i;
- sort(e+,e+m+,cmp);
- for(int i=;i<=m;i++)
- {
- if(e[i].x==||e[i].y==) continue;
- int x=find(e[i].x),y=find(e[i].y);
- if(x==y) continue;
- f[x]=y;
- check[e[i].x][e[i].y]=check[e[i].y][e[i].x]=;
- }
- }
- void solve1()
- {
- for(int i=;i<=n;i++) Min[i]=INF;
- for(int i=;i<=n;i++)
- if(a[][i]!=-)
- {
- int t=find(i);
- if(a[i][]<Min[t])
- {
- Min[t]=a[i][];
- temp[t]=i;
- }
- }
- for(int i=;i<=n;i++)
- if(Min[i]!=INF)
- {
- md++;
- check[][temp[i]]=check[temp[i]][]=;
- }
- }
- void dfs(int x,int fa)
- {
- for(int i=;i<=n;i++)
- if(check[x][i]&&i!=fa)
- {
- if(dp[i].v==-)
- {
- if(a[x][i]<dp[x].v) dp[i]=dp[x];
- else dp[i].x=x,dp[i].y=i,dp[i].v=a[x][i];
- }
- dfs(i,x);
- }
- }
- void solve2()
- {
- for(int i=md+;i<=k;i++)
- {
- for(int i=;i<=n;i++) dp[i].v=-; dp[].v=-INF;
- for(int j=;j<=n;j++) if(check[][j]) e[j].v=-INF;
- dfs(,); int t=; double minn=INF;
- for(int j=;j<=n;j++)
- if(a[][j]!=-&&a[][j]-dp[j].v<minn)
- {
- minn=a[][j]-dp[j].v;
- t=j;
- }
- if(minn>=) break;
- int x=dp[t].x,y=dp[t].y;
- check[][t]=check[t][]=;
- check[x][y]=check[y][x]=;
- }
- }
- void pre()
- {
- n=m=k=md=; ans=;
- memset(check,,sizeof(check));
- }
- int main()
- {
- //freopen("cin.in","r",stdin);
- //freopen("cout.out","w",stdout);
- int CASE=read();
- while(CASE--)
- {
- pre();
- init();
- Kruskal();
- solve1();
- solve2();
- for(int i=;i<=n;i++)
- for(int j=;j<=n;j++)
- if(check[i][j]&&a[i][j]>ans) ans=a[i][j];
- printf("%.2lf\n",ans);
- }
- return ;
- }
最小k度限制生成树的更多相关文章
- 【POJ 1639】 Picnic Planning (最小k度限制生成树)
[题意] 有n个巨人要去Park聚会.巨人A和先到巨人B那里去,然后和巨人B一起去Park.B君是个土豪,他家的停车场很大,可以停很多车,但是Park的停车场是比较小.只能停k辆车.现在问你在这个限制 ...
- poj 1639 最小k度限制生成树
题目链接:https://vjudge.net/problem 题意: 给各位看一下题意,算法详解看下面大佬博客吧,写的很好. 参考博客:最小k度限制生成树 - chty - 博客园 https:/ ...
- poj1639 Picnic Planning,K度限制生成树
题意: 矮人虽小却喜欢乘坐巨大的轿车,车大到能够装下不管多少矮人.某天,N(N≤20)个矮人打算到野外聚餐.为了集中到聚餐地点,矮人A 要么开车到矮人B 家中,留下自己的轿车在矮人B 家,然后乘坐B ...
- 最小k度最小生成树模板
代码是抄的 题解是瞄的 可我想学习的心是真的嘤嘤嘤 然而 还是上传一份ioi大神的论文吧 链接:https://pan.baidu.com/s/1neIW9QeZEa0hXsUqJTjmeQ 密码:b ...
- Picnic Planning POJ - 1639(最小k度生成树)
The Contortion Brothers are a famous set of circus clowns, known worldwide for their incredible abil ...
- K度限制MST poj 1639
/* k度限制MST:有一个点的度<=k的MST poj 1639 要求1号点的度不超过k 求MST 我们先把1号点扔掉 跑MST 假设有sum个连通分支 然后把这sum个分支连到1上 就得到了 ...
- HDU 4862 Jump(最小K路径覆盖)
输入一个n×m网格图,每个结点的值为0-9,可以从任意点出发不超过k次,走完每个点且仅访问每个结点一次,问最终的能量最大值.不可全部走完的情况输出-1. 初始能量为0. 而结点(x,y)可以跳跃到结点 ...
- 求n个数中的最大或最小k个数
//求n个数中的最小k个数 public static void TestMin(int k, int n) { Random rd = new Ra ...
- nyoj 678 最小K个数之和
最小K个数之和 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描述 输入n个整数,输出其中最小的K个数之和.例如输入4,5,1,1,6,2,7,3,3这9个数字,当k=4 ...
随机推荐
- jqGrid 获取多级标题表头
1.jgGrid没有提供此方法获取如下标题 2.实现代码 getHeaders:function(){ var headers=[],temptrs=[]; //select the group he ...
- leetcode1016
class Solution(object): def queryString(self, S: str, N: int) -> bool: return all(S.find(bin(i)[2 ...
- zipfile模块
在python中操作zip文件, 基本上都是使用zipfile模块,他可以创建.解压文件,获取zip文件的元数据信息. 我们想要操作一个zip文件,第一步就是初始化ZipFile实例. 1.打开tes ...
- js实现上传前删除指定图片
"上传之前"移除选错图片代码: 此处效果为:点击需要删除的图片,确认删除就可以了.
- block原理
block原理 block的本质是一个结构体,包含引用的外部变量及一个需要执行的函数的函数指针,在内存中可以有三个位置,即堆上.栈上和全局区(静态区).当block中没有引用外部变量时,block的位 ...
- jquery接触初级-----juqery DOM操作 之二
DOm 操作之: 1.1 children(),这个函数只是查找元素的子元素,而不考虑其他后代元素 <body> <p title="请选择你最喜欢的水果"&g ...
- Linux性能测试分析命令_iostat
iostat用于输出CPU和磁盘I/O相关的统计信息 iostat语法 用法:iostat [ 选项 ] [ <时间间隔> [ <次数> ]] 常用选项说明: -c:只显示系统 ...
- 遍历DOM树,理解更新范围
在JavaScript中,如果需求对多个元素重复进行同样的操作,就需要写一个循环来遍历选中的所有元素. 在jQuery中,当选择器返回了多个元素时,可以使用一个方法来更新所有的元素,不再需要使用循环. ...
- C# winform进度条 (异步)
进度条页面: http://www.cnblogs.com/Deckard/archive/2009/06/24/1510451.html //============================ ...
- [PHP]更新中间关联表数据的两种思路
---------------------------------------------------------------------------------------------------- ...