【题目描述】

给你一个图,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度限制生成树的更多相关文章

  1. 【POJ 1639】 Picnic Planning (最小k度限制生成树)

    [题意] 有n个巨人要去Park聚会.巨人A和先到巨人B那里去,然后和巨人B一起去Park.B君是个土豪,他家的停车场很大,可以停很多车,但是Park的停车场是比较小.只能停k辆车.现在问你在这个限制 ...

  2. poj 1639 最小k度限制生成树

    题目链接:https://vjudge.net/problem 题意: 给各位看一下题意,算法详解看下面大佬博客吧,写的很好. 参考博客:最小k度限制生成树 - chty - 博客园  https:/ ...

  3. poj1639 Picnic Planning,K度限制生成树

    题意: 矮人虽小却喜欢乘坐巨大的轿车,车大到能够装下不管多少矮人.某天,N(N≤20)个矮人打算到野外聚餐.为了集中到聚餐地点,矮人A 要么开车到矮人B 家中,留下自己的轿车在矮人B 家,然后乘坐B ...

  4. 最小k度最小生成树模板

    代码是抄的 题解是瞄的 可我想学习的心是真的嘤嘤嘤 然而 还是上传一份ioi大神的论文吧 链接:https://pan.baidu.com/s/1neIW9QeZEa0hXsUqJTjmeQ 密码:b ...

  5. Picnic Planning POJ - 1639(最小k度生成树)

    The Contortion Brothers are a famous set of circus clowns, known worldwide for their incredible abil ...

  6. K度限制MST poj 1639

    /* k度限制MST:有一个点的度<=k的MST poj 1639 要求1号点的度不超过k 求MST 我们先把1号点扔掉 跑MST 假设有sum个连通分支 然后把这sum个分支连到1上 就得到了 ...

  7. HDU 4862 Jump(最小K路径覆盖)

    输入一个n×m网格图,每个结点的值为0-9,可以从任意点出发不超过k次,走完每个点且仅访问每个结点一次,问最终的能量最大值.不可全部走完的情况输出-1. 初始能量为0. 而结点(x,y)可以跳跃到结点 ...

  8. 求n个数中的最大或最小k个数

    //求n个数中的最小k个数        public static void TestMin(int k, int n)        {            Random rd = new Ra ...

  9. nyoj 678 最小K个数之和

    最小K个数之和 时间限制:1000 ms  |  内存限制:65535 KB 难度:2   描述 输入n个整数,输出其中最小的K个数之和.例如输入4,5,1,1,6,2,7,3,3这9个数字,当k=4 ...

随机推荐

  1. Android呼吸灯效果实现

    最近需要做一个搜索动画的呼吸灯效果,在网上查了下有以下两种实现方式,现记录下来. 实现呼吸灯效果本质上都是使用ImageView动画,实现alpha值变化. 第一种方式,直接使用动画实现,代码如下: ...

  2. Python linux 上的管理工具 pyenv 安装, pip 使用, python项目(版本分割, 项目分割, 虚拟环境创建)

    01: 假设你有一个最小环境安装的 centos-6.x 的linux操作系统 02: 安装 git => yum -y install git 03: 安装依赖 => yum -y in ...

  3. jqGrid 获取多级标题表头

    1.jgGrid没有提供此方法获取如下标题 2.实现代码 getHeaders:function(){ var headers=[],temptrs=[]; //select the group he ...

  4. Memcache操作类

    using Memcached.ClientLibrary; using System; using System.Collections.Generic; using System.Linq; us ...

  5. NRF51822之RNG

    在裸机下官方已经提供另一个RNG的例子(RF51_SDK_10.0.0_dc26b5e\examples\peripheral\rng) 好了现在我将给出在蓝牙模式下如何使用例子 #include & ...

  6. 【JEECG技术文档】JEECG 组织机构导入V3.7

    1.功能介绍 组织机构导入 提供组织机构模版导入功能,使用户更快速的创建组织机构 要使用组织机构导入功能需要完成以下步骤: 1. 下载模版excel 2. 填写组织机构信息 3. 点击导入-选择文件- ...

  7. maven插件之checkstyle

    checkstyle的eclipse插件已经在eclipse中有讲过,用于开发者自查用.但是不能保证开发者能按照checkstyle进行改正或者自查,因此就需要对未checkstyle通过的代码不能编 ...

  8. APP-8.1-百度语音应用

    1.百度语音登录地址 http://yuyin.baidu.com/ 2.控制台创建应用 3.生成签名 3.1Postman软件应用 APP-8.2-Postman应用 3.2Postman执行 UR ...

  9. java 中AIO,BIO,NIO的区别(茅塞顿开)

    看到知乎上一篇回答,解决了疑惑:https://www.zhihu.com/question/56673416 第三位作者的回答...原谅我没有登录知乎,不然一定给他留赞. 也可以参考:https:/ ...

  10. Hbase数据读写流程

    From: https://blog.csdn.net/wuxintdrh/article/details/69056188 写操作: Client写入,存入Memstore,Memstore满则Fl ...