题目大意是:
矮人虽小却喜欢乘坐巨大的轿车,轿车大到可以装下无论多少矮人。某天,N(N≤5000)个矮人打算到野外聚餐。为了集中到聚餐地点,矮人A要么开车到矮人B家中,留下自己的轿车在矮人B家,然后乘坐B的轿车同行;要么直接开车到聚餐地点,并将车停放在聚餐地。
虽然矮人的家很大,可以停放无数量轿车,但是聚餐地点却最多只能停放K辆轿车。现在给你一张加权无向图,它描述了N个矮人的家和聚餐地点,要你求出所有矮人开车的最短总路程。
[输入文件]
第一行是整数M(M<=49000),接下来M行描述了M条道路。每行形式如同:S1 S2 x,S1和S2均是大于0小于5000的整数,x小于等于1000。最后一行包含两个整数k,root。root表示聚餐的地点。
[输出文件]
仅一行,形式如同:Total miles driven: xxxXxx是整数,表示最短总路程。
3. 算法
这是一道标准的最小限度生成树(就是图中的某些点在生成树时有度的限制,找满足这些约束的最小生成树。如果所有点都有度限制,那么这个问题将是NP难题),具体算法如下:
1. 先求出最小 m 度限制生成树:原图中去掉和 V0 相连的所有边(可以事先存两个图, Ray 的方法是一个邻接矩阵,一个邻接表,用方便枚举边的邻接表来构造新图),得到 m 个连通分量,则这 m 个连通分量必须通过 v0 来连接,所以,在图 G 的所有生成树中 dT(v0)≥m 。也就是说,当 k<m 时,问题无解。对每个连通分量求一次最小生成树(哪个算法都行),对于每个连通分量 V’ ,用一条与 V0 直接连接的最小的边把它与 V0 点连接起来,使其整体成为一个生成树。于是,我们就得到了一个 m 度限制生成树,不难证明,这就是最小 m 度限制生成树。
2. 由最小 m 度限制生成树得到最小 m+1 度限制生成树;:连接和 V0 相邻的点 v ,则可以知道一定会有一个环出现(因为原来是一个生成树),只要找到这个环上的最大权边(不能与 v0 点直接相连)并删除,就可以得到一个 m+1 度限制生成树,枚举所有和 V0 相邻点 v ,找到替换后,增加权值最小的一次替换 (当然,找不到这样的边时,就说明已经求出) ,就可以求得 m+1 度限制生成树。。如果每添加一条边,都需要对环上的边一一枚举,时间复杂度将比较高(但这个题数据比较小,所以这样也没问题,事实上,直接枚举都能过这个题),这里,用动态规划解决。设 Best(v) 为路径 v0—v 上与 v0 无关联且权值最大的边。定义 father(v) 为 v 的父结点,由此可以得到动态转移方程: Best(v)=max(Best(father(v)),ω(father(v),v)) ,边界条件为 Best[v0]=-∞ (因为我们每次寻找的是最大边,所以 -∞ 不会被考虑) ,Best[v’]=-∞| (v0,v’)∈E(T) 。
3. 当 dT(v0)=k 时停止(即当 V0 的度为 k 的时候停止),但不一定 k 的时候最优。
接下来说一下算法的实现:
采用并查集+ kruskal 代码量还少一点。
首先, 每个连通分量的的最小生成树可以直接用一个循环,循环着 Kruskal 求出。这里利用了联通分量间的独立性,对每个连通分量分别求最小生成树,和放在一起求,毫不影响。而且kruskral算法保证了各连通分量边的有序性。
找最小边的时候,上面讲的动态规划无疑是一种好方法,但是也可以这么做:
先走一个循环,但我们需要逆过来加边,将与v0关联的所有边从小到达排序,然后将各连通分量连接起来,利用并查集可以保证每个连通分量只有一条边与v0相连,由于边已经从小到达排序,故与每个连通分量相连的边就是每个连通分量与v0相连中的最小边。
然后求 m+1 度的最小生成树时,可以直接用 DFS ,最小生成树要一直求到 k 度,然后从中找出一个最优值。
4. 注意事项
5. 时空复杂度
6. 程序代码
并查集+kruskal (C++)
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define inf 0xffffff
#define NUM 22
struct info{
int s,e,w,flag,next,ith;
}edge[NUM * NUM],item[NUM],edge2[NUM * NUM]; // edge排序要用到,且存所有边,item存与v0相关联的边,edge2备份所有边
char peo[NUM][15];
int first[NUM];
int f[NUM];
int sign[NUM];
int tol,n;
void Add(int s,int e,int w)
{
edge[tol].s = s;
edge[tol].e = e;
edge[tol].w = w;
edge[tol].flag = 0;
edge[tol].ith = tol;
edge2[tol] = edge[tol];
edge2[tol].next = first[s];
first[s] = tol ++;
}
int find(char *s)
{
int i;
for(i = 0; i < n; i ++){
if( !strcmp(s,peo[i]) ) return i;
}
strcpy(peo[n ++],s);
return i;
}
int cmp(const void *a,const void *b)
{
struct info *c,*d;
c = (struct info *)a;
d = (struct info *)b;
return c->w - d->w;
}
int find_father(int s)
{
if(s != f[s]){
f[s] = find_father(f[s]);
}
return f[s];
}
void Union(int a,int b)
{
f[f[a]] = f[b];
}
int v0;
int dfs(int s,int max,int e) // s待扩展的点,max扩展到s时的最大边权,e最大边权对应的边号
{
int temp,k,t,mid;
sign[s] = 1;
for(temp = first[s];temp != -1;temp = edge2[temp].next ){
if(edge2[temp].flag && !sign[edge2[temp].e]){
if(edge2[temp].e == v0) return e;
t = edge2[temp].w > max ? temp : e;
k = edge2[temp].w > max ? edge2[temp].w : max;
t = dfs(edge2[temp].e, k , t);
if( t != -1 ) return t;
sign[edge2[temp].e] = 0;
}
}
return -1;
}
int main()
{
int m,i,j,w,vn,count,min,ans;
int fa,fb,res,temp,k,max_e,mid_i;
char nam[2][15];
n = tol = 0;
memset(first,-1,sizeof(first));
scanf("%d",&m);
while(m --){
scanf("%s%s%d",nam[0],nam[1],&w);
i = find(nam[0]);
j = find(nam[1]);
Add(i,j,w); //添加正向边和反向边
Add(j,i,w);
}
scanf("%d",&k);
v0 = find("Park");
for(temp = first[v0],i = 0;temp != -1; temp = edge2[temp].next,i ++) item[i] = edge2[temp];
vn = i;
for(i = 0; i < n;i ++)f[i] = i;
qsort(edge,tol,sizeof(struct info),cmp);
for(res = i = 0; i < tol;i ++){ //第2步
if(edge[i].s == v0 || edge[i].e == v0) continue;
fa = find_father(edge[i].s);
fb = find_father(edge[i].e);
if(fa != fb){
Union(edge[i].s,edge[i].e);
edge2[ edge[i].ith ].flag = 1;
edge2[ edge[i].ith^1 ].flag = 1;
res += edge[i].w;
}
}
qsort(item,vn,sizeof(struct info),cmp);
for(count = i = 0; i < vn;i ++){ //第3步 复制第2步稍加修改即可
fa = find_father(item[i].s);
fb = find_father(item[i].e);
if(fa != fb){
Union(item[i].s,item[i].e);
edge2[item[i].ith].flag = 1;
edge2[ item[i].ith^1 ].flag = 1;
res += item[i].w;
count ++;
}
}
ans = res; // ans记录最终的答案
while(count < k){ // 扩展度
min = inf;
for(i = first[v0];i != -1;i = edge2[i].next ){
if(edge2[i].flag == 0){
memset(sign,0,sizeof(sign));
temp = dfs(edge2[i].e,-inf,i); //返回最大权值边的编号,这里本没有树,因为边的存在而有了树,
if(res - edge2[temp].w + edge2[i].w < min){ ////////////////做到心中有“树”
min = res - edge2[temp].w + edge2[i].w;
mid_i = i; //记录要加入边的序号
max_e = temp; //要删除边的序号
}
}
}
if(min == inf) break;
edge2[mid_i].flag = 1; //加
edge2[mid_i^1].flag = 1;
edge2[max_e].flag = 0; //删
edge2[max_e^1].flag = 0;
res = min;
if(res < ans)ans = res; //取最小
count ++;
}
printf("Total miles driven: %d\n",ans);
return 0;
}
POJ 1639(K度限制的生成树)
最新推荐文章于 2021-05-11 17:15:21 发布