最小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 ...
随机推荐
- c# GC 新典型
public class testGC : MonoBehaviour { class XDict<K, V> { public void TryGetValue(K key, V val ...
- Python爬虫示例
#!/usr/bin/python #coding:utf8 import re import urllib def gethtml(url): page=urllib.urlopen(url) ht ...
- 【381】python 获取列表中重复元素的索引值
参考:获取python的list中含有重复值的index方法_python_脚本之家 核心思想:建立字典,遍历列表,把列表中每个元素和其索引添加到字典里面 cc = [1, 2, 3, 2, 4] f ...
- 四则运算3+PSP
题目要求: 1.要求在第二次实验(四则运算2)的基础上加上其他功能. 2.要求能够在每个运算式打印出来后,用户能够进行输入计算的答案,并且程序进行判断给出用户输入的答案的正确性. 3.要求实现四则混合 ...
- Android 性能优化 SparseArray【转载】
原文地址:Android学习笔记之性能优化SparseArray 学习内容: 1.Android中SparseArray的使用.. 昨天研究完横向二级菜单,发现其中使用了SparseArray去替 ...
- RxJava2.0学习笔记2 2018年7月3日 周二
摘记: 1.map -- 转换 有些服务端的接口设计,会在返回的数据外层包裹一些额外信息,这些信息对于调试很有用,但本地显示是用不到的.使用 map() 可以把外层的格式剥掉,只留下本地会用到的核心 ...
- JS代码注释
1.css和js都可以使用/**/进行注释 2.html使用<!---->注释 3.单行js代码可以使用//进行注释 <!DOCTYPE html> <html lang ...
- 带报表的asp.net项目不要升级
原来项目是用vs2008开发的,框架是3.5的.刚去公司项目就感觉比较乱,来来去去了几波人.所以我想统一把它升级成vs2010框架4.0. 结果页面经常报错,什么脚本找不到不存.后台脚本是用这句来添加 ...
- eclipse 访问GitHub 问题
- C# WCF初识
原文:http://www.cnblogs.com/artech/archive/2007/02/26/656901.html 方式1: 需引用 System.ServiceModel namespa ...