P300-野餐计划(POJ-1639最小度限制生成树)

黑书上的例题,具体模型是求一个无向图的最小生成树,其中有一个点的度有限制(假设为 k)。

 

要求最小 k 度生成树,我们可以按照下面的步骤来做:

设有度限制的点为 V0 ,V0称为根节点

1,把所有与 V0 相连的边删去,图会分成多个子图(假设为 m 个,显然的,如果 m > k,那么问题无解),让他们分别求最小生成树;然后用最小的代价将 m 个最小生成树和 V0 连起来,那我们就得到了一棵关于 V0 的最小 m 度生成树。

2,在 m 度生成树中找一个点和 V0 相连(设这条边的权值为 a),会生成一个环,为了满足最小生成树的要求,我们必须删掉一条边(设这条边的权值为 b),以使总权值尽量小,那么就要求 a 尽量的小,b 尽量的大。

完成一次 2 的操作后得到的是 m+1 度最小生成树,以此类推,直到得到最小 k 度生成树。


PS:这道题并不是要求 k 度的最小生成树,而是要求根节点的度在不超过 k 值的情况下,该图的最小生成树。也就是说,不一定要求到 k 度生成树,只要图的总权值不能继续减小我们就可以停下来了。

代码是“拿来”的,因为用map处理数据,所以代码量小了一点,而且方便的多了,STL是真的很强大,orz……

/******************************************************************************************************
 ** Copyright (C) 2011.07.01-2013.07.01
 ** Author: famousDT <13730828587@163.com>
 ** Edit date: 2011-12-20
******************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>//abs,atof(string to float),atoi,atol,atoll
#include <math.h>//atan,acos,asin,atan2(a,b)(a/b atan),ceil,floor,cos,exp(x)(e^x),fabs,log(for E),log10
#include <vector>
#include <queue>
#include <map>
#include <time.h>
#include <set>
#include <list>
#include <stack> 
#include <string>
#include <iostream>
#include <fstream>
#include <assert.h>
#include <bitset>
#include <iterator>//C++Primer
#include <string.h>//memcpy(to,from,count
#include <ctype.h>//character process:isalpha,isdigit,islower,tolower,isblank,iscntrl,isprll
#include <numeric>
#include <functional>
#include <algorithm>
using namespace std;

typedef long long int ll;

#define MY_PI acos(-1)
#define MY_MAX(a, b) ((a) > (b) ? (a) : (b))
#define MY_MIN(a, b) ((a) < (b) ? (a) : (b))
#define MY_MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))
#define MY_ABS(a) (((a) >= 0) ? (a) : (-(a)))
#define MY_INT_MAX 0x7fffffff

/*==========================================================*\
| 
\*==========================================================*/
const int N = 21;

struct node {
	int v, cost;//点v, dist[v]
	node() {}
	node(int x, int y) : v(x), cost(y) {}
};

bool operator < (const node &l, const node &r)
{
	return l.cost > r.cost;
}

priority_queue<node> q;
map<string, int> Map;
int mat[N][N], dist[N], clo[N], pre[N], fst[N], max_side[N];
int n, K; 
  
int prim(int s, int id)
{
	while (!q.empty()) q.pop();
	dist[s] = 0;
	q.push(node(s, 0));
	int i, res = 0;
	while (!q.empty()) {
		node cur = q.top();
		q.pop();
		int u = cur.v;
		if (!clo[u]) {
			clo[u] = id;
			res += dist[u];
			for (i = 1; i < n; ++i) {
				if (!clo[i] && mat[u][i] && mat[u][i] < dist[i]) {//满足松弛条件
					pre[i] = u;
					dist[i] = mat[u][i];
					q.push(node(i, dist[i]));
				}
			}
		}
	}
	return res;
}

void update(int cur, int last, int maxside)
//也是一个dfs过程,直到搜回到起点,同时完成了max_side[]更新
{
	max_side[cur] =  maxside > mat[cur][last] ? maxside : mat[cur][last];
	int i;
	for (i = 1; i < n; ++i) {
		if (last != i && mat[cur][i] && (pre[cur] == i || pre[i] == cur))
			update(i, cur, max_side[cur]);
	}
}

void solve()
{
	int i, res, cnt;
	for (i = 0; i < n; ++i) {
		dist[i] = MY_INT_MAX;
		clo[i] = pre[i] = fst[i] = 0;
	}
	res = 0;
	cnt = 1;//除去根节点后,图中的连通子图个数,即最小生成树个数
	for (i = 1; i < n; ++i) {
		if (!clo[i]) {
			res += prim(i, cnt++);
		}
	}
	for (i = 1; i < n; ++i) {//找到每个生成树和Park最近的点使之和Park相连
		int id = clo[i];
		if (mat[0][i] && (!fst[id] || mat[0][i] < mat[0][fst[id]]))
			fst[id] = i;
	}
	for (i = 1; i < cnt; ++i) {//把m个生成树上和根节点相连的边加入res,得到关于Park的最小m度生成树
		res += mat[0][fst[i]];
		mat[0][fst[i]] = mat[fst[i]][0] = 0;//之所以用邻接阵就是因为删除边很方便
		update(fst[i], 0, 0);
	}
    /* 添删操作:将根节点和生成树中一个点相连,会产生一个环,将这个环上(除刚添的那条边外)权值最大 
	   的边删去.由于每次操作都会给总权值带来影响d=max_side[tmp]-mat[0][tmp],我们需要得到最小生 
	   成树,所以我们就要求 d 尽量大 
    */ 
	K = K - cnt + 1;//接下来重复操作,直到度数满足条件
	while (K--) {
		int tmp = 0;
		for (i = 1; i < n; ++i) {//找d值最大的点(就是说完成添删操作后可以使总边权减小的值最大)
			if (mat[0][i] && (tmp == 0 || max_side[tmp] - mat[0][tmp] < max_side[i] - mat[0][i]))
				tmp = i;
		}
		if (max_side[tmp] <= mat[0][tmp]) break;//总权值无法再减小
		res -= max_side[tmp] - mat[0][tmp];
		mat[0][tmp] = mat[tmp][0] = 0;
		int p = 0;
		for (i = tmp; pre[i]; i = pre[i])
			if (p == 0 || mat[p][pre[p]] < mat[i][pre[i]])
				p = i;
		pre[p] = 0;
		update(tmp, 0, 0);
	}
    printf("Total miles driven: %d\n", res);
}  
 
int main()
{
	int m, weight;
	string a, b;
	while (~scanf("%d", &m)) {
		Map["Park"] = 0;
		n = 1;
		memset(mat, 0, sizeof(mat));
		while (m--) {
			cin>>a>>b>>weight;
			if (!Map.count(a)) Map[a] = n++;
			if (!Map.count(b)) Map[b] = n++;
			int u = Map[a];
			int v = Map[b];
			if (!mat[u][v] || weight < mat[u][v])
				mat[u][v] = mat[v][u] = weight;
		}
		scanf("%d", &K);
		solve();
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值