【题目描述】

给你一个图,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. centos使用记

    20180404:今天在笔记本上安装了centos6.9,第一次安装的7.4时进不了安装界面,后来下载了6.9版的,可以安装.安装完后启动时出现fail reg的错误,然后提示登陆,不知是用户名为:r ...

  2. git 找回本地误删的文件

    1, 查看本地工作区变化 => git status 2, 重新设置文件状态 =>  git reset HEAD url    ( url 是上一步第二个红框中的地址) 3, 检出文件 ...

  3. githup创建新java项目

    1.在githup中创建仓库 2.import创建的地址到本地文件d:/mygit 3.在d:/mygit中创建eclipse项目 3.在eclipse中team-->push to branc ...

  4. leetcode140

    class Solution(object): def wordBreak(self, s, wordDict): """ :type s: str :type word ...

  5. Lua脚本语法说明(转)

    Lua脚本语法说明(增加lua5.1部份特性) Lua 的语法比较简单,学习起来也比较省力,但功能却并不弱. 所以,我只简单的归纳一下Lua的一些语法规则,使用起来方便好查就可以了.估计看完了,就懂得 ...

  6. js判断网页是否加载完毕

    1. document.onreadystatechange = function () { if(document.readyState=="complete") { docum ...

  7. js 弹窗的实现

    原理: 1. 点击按钮,触发窗口显示,遮罩层显示,并设置窗口的位置 2. 为弹出的窗口绑定鼠标滚动事件和视窗改变事件 3.点击关闭按钮,弹窗消失,遮罩层消失 html 代码: <!DOCTYPE ...

  8. week5 04 npm run build

    上期 我们完成了nodeserver的创建 用的是express genrealtor那个工具 我们在server端执行 起来了 然后我们改一下 删一下 我们觉着暂时没用的东西 首先去app.js程序 ...

  9. SqlServer数据库碎片整理——BCC SHOWCONTIG

    SQLServer提供了一个数据库命令——DBCC SHOWCONTIG——来确定一个指定的表或索引是否有碎片.  示例: DBCC SHOWCONTIG语法: 显示指定的表的数据和索引的碎片信息.  ...

  10. hasattr() getattr() setattr() 函数使用方法

    1. hasattr(object, name) 判断object对象中是否存在name属性,当然对于python的对象而言,属性包含变量和方法:有则返回True,没有则返回False:需要注意的是n ...