有度数限制的最小生成树问题--Picnic Planning

目前为止见过的最复杂的题了(对代码实现能力要求很高)。。。。。码了好久才想通。

接下来来剖析步骤--

1.先不考虑1号节点,跑一遍克鲁斯卡尔,这样得到的就是各个联通块内部的最小生成树(也就是森林)。

2,。贪心的想,将每个联通块内到根节点最短的端点找出并连接。

3. 连接之后我们假设用了t条边,而题目要求使用s条边,那么我们就还有(s-t)条边可以使用,因此我们可以考虑用与1直接相连的边的权值去删掉(1,x)中边权最大的边,(这样既可保持联通性,又减少了权值)。

4.关键在于如何实现找到路径上的最值。

我们看这段代码,乍看会tle,实则不然,我们用tree函数储存原本就在生成树内的边(也就是我们要讨论的点) ,这个时候由于生成树的特性,遍历到叶子节点的时候就会停止遍历了。(可以画张图看一下。)(可以理解成把一个树从上到下更新了一遍。

好了,分析完毕,以后必不会再写了,,当成板子收起来。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
struct  rec
{
	long long  x,y,z;
} edge[5000],dp[5001];
int n,m,ans;
int  fa[10000];

int tot;
map<string ,int > name;
vector<int> block[1000];
int tree[1000][1000];
int mind[1000];
long long  g[1000][1000];
bool vis[101];
int  all;
bool operator <(rec a,rec b)
{
	return a.z < b.z;
}

int get(int x)
{
	if(x == fa[x]) return x;
	return fa[x] = get(fa[x]);
}

void kruskal() //生成的是一颗森林
{
	for(int i = 1 ; i <= n ; i++)
	{
		int x = get(edge[i].x), y =get(edge[i].y);
		if(x == y || edge[i].x == 1 || edge[i].y == 1)continue ;
		fa[x] = y;
		tree[edge[i].x][edge[i].y] = tree[edge[i].y][edge[i].x] = 1;
		ans += edge[i].z;
	}

}
//穷尽所有可能,去更新能到达该点的最值
void dfs(int cur, int pre) {
  //  cout << cur << " " << pre << endl;
	for (int i = 2; i <= tot; i++) {
     //   cout << i << endl;
		if (i == pre || !tree[cur][i]){ continue;}
	
			if (dp[cur].z > g[cur][i]) dp[i] = dp[cur];
			else {
				dp[i].x = cur;
				dp[i].y = i;
				dp[i].z = g[cur][i];
			}
		    
		    dfs(i, cur);
	}
}
void solve()
{
	int point[101];
	for(int i = 2; i <= tot ; i++)
	{
		if(g[1][i] !=INF)
		{
			if(g[1][i] < mind[get(i)])
			{
				mind[get(i)] = g[1][i];
				point[get(i)] = i;
			}
		}
	}
   
	//各连通块与1连边。
	
	int cnt = 0;//联通块数量
	for(int i = 2 ; i <= tot ; i++)
	{
		if(!vis[get(i)]) 
		{
			vis[get(i)] = 1;
			ans += mind[get(i)];
			tree[1][point[get(i)]] = tree[point[get(i)]][1] = 1;
			cnt++;
		}
	}
   // cout <<ans << endl;
	for(int i = cnt + 1 ; i <= all ; i++)
	{
		memset(dp,-1,sizeof dp);
			// maxx w - z;
		dp[1].z = -INF;
		for(int j = 2 ; j <= tot ; j++) if(tree[1][j]) dp[j].z = -INF;
		dfs(1,0);
		int idx , minx = INF;
		for(int j = 2 ; j <= tot ; j++)
		{
			if(minx >   g[1][j] - dp[j].z)
			{
				minx = g[1][j] - dp[j].z;
				idx = j;
			}
		}
		if(minx >= 0) break;
		tree[1][idx] = tree[idx][1] = 1;
		tree[dp[idx].x][dp[idx].y] =  tree[dp[idx].y][dp[idx].x] = 0;
		ans += minx;
	}






}

int main()
{ 
    memset(tree,0,sizeof tree);
  	memset(g,0x3f,sizeof g);
  	memset(edge,0x3f,sizeof edge);
  	memset(mind,0x3f,sizeof mind);
	cin >> n;
	name["Park"] = ++tot;
	//for(int i = 1; i <= n ; i++)
	for(int i = 1;  i <= n ; i++)
	{
		string s1,s2;
		long long   d;
		cin >> s1 >> s2 >> d;
		if(!name[s1]) name[s1] = ++tot;
	    if(!name[s2]) name[s2] = ++tot;
	    g[name[s1]][name[s2]] = g[name[s2]][name[s1]] = min(d,g[name[s1]][name[s2]]);
	    edge[i].x = name[s1],edge[i].y = name[s2],edge[i].z = d;
	}
	sort(edge + 1 , edge + 1 + n);	//边
	cin >> all;
	for(int i = 1 ; i <= tot ; i++) fa[i]  = i;
	kruskal(); 
    solve();
	printf("Total miles driven: %d\n", ans);



}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值