HDU4126 Genghis Khan the Conqueror——最小生成树+树形DP(⭐)

点这里

题意: 给定一个带权无向图G,n个点m条边,每条边都有一个权值w。题目保证两个点之间最多只有一条边。接下来有Q次边权的改变,且改变后的权值之后一定比原来的更大。求每次改变边权之后的所有最小生成树的平均值(边权的改变前后不影响。


题解:

  • 构造最小生成树: 在没有任何边权改变之前,求出最原始的最小生成树,记录这棵树的权值和ans
  • 边权替换: 再次强调,每次边权的改变,是独立操作、前后互补影响的。明确之后再对改变的边权进行分类讨论:
    • 边在树上: 这次改变的边在最原始的最小生成树上,并且题目保证改变的边权一定比原来的大,所以一定会影响最小生成树的结果。办法是在原始的最小生成树上,断开这条改变的边,从分开的两棵树,找到一条连接两棵树的最小边。则新的生成树的权值和 = ans - 改变的边(原来的值) + 连接两棵树的最小边
    • 边不在树上: 如果改变的边不在原始的最小生成树上,那其实是不会影响结果的,改变后的最小生成树还是原来的生成树,那么权值和 = ans
  • 连接两棵子树的最小边: 既然我们决定将最小生成树上那条改变的边断开,那么这棵树就会分成两棵子树,但是要如何找到连接两棵子树的最小边?
    • 树形DP: 定义状态dp[u][v]表示点u在的这棵子树到点v在的这颗子树的最小边权。(树形DP这块我解释不下去了,看代码吧)

过程中犯的错:

  • 数据范围: 又一次,我把数组开小了。但是这个可恶的题目告诉我是TLE。害我一直在想办法减小时间复杂度,直到实在优化不了了,才发小数组开小了😢。
  • vector E[N]: 用于存放最小生成树上点i连接的所有点,包括子节点和父节点

#include<bits/stdc++.h>
using namespace std;
const int N = 3e3 + 10;
const int inf = 0x3f3f3f3f;
typedef long long ll;

int n, m, Q, ans;
int u, v, w;
int pre[N];
int G[N][N];
int dp[N][N];
int d[N], vis[N];
vector<int> E[N];
struct edge{
	int u, v, w;
	bool operator<(const edge &a)const{return a.w < w;}
};
void prim(){
	priority_queue<edge> que;
	for(int i = 0; i < n; i++)	vis[i] = pre[i] = 0, d[i] = G[0][i], E[i].clear(), que.push({0, i, d[i]});
	ans = 0, vis[0] = 1, pre[0] = -1, d[0] = inf;
	while(!que.empty()){
		edge e = que.top(); que.pop();
		if(vis[e.v])	continue;
		ans += d[e.v];
		vis[e.v] = 1;
		pre[e.v] = e.u;
		E[e.u].push_back(e.v);	E[e.v].push_back(e.u);
		for(int i = 0; i < n; i++)	if(!vis[i] && d[i] > G[e.v][i])	d[i] = G[e.v][i], que.push({e.v, i, d[i]});
	}
}
int dfs(int u, int fa, int root){
	int res = inf;
	for(int i = 0; i < E[u].size(); i++){
		int v = E[u][i];
		if(v == fa)	continue;
		int tem = dfs(v, u, root);
		res = min(res, tem);
		dp[u][v] = dp[v][u] = min(dp[u][v], tem);
	}
	if(root != fa)	res = min(res, G[root][u]);
	return res;
}
int main(){
	while(~scanf("%d%d", &n, &m) && n && m){
		for(int i = 0; i < n; i++)	for(int j = 0; j < n; j++)	G[i][j] = dp[i][j] =  inf;
		for(int i = 0; i < m; i++){
			scanf("%d%d%d", &u, &v, &w);
			G[u][v] = G[v][u] = w;
		} 
		prim();
		for(int i = 0; i < n; i++)	dfs(i, -1, i);
		ll sum = 0;
		scanf("%d", &Q);
		for(int i = 1; i <= Q; i++){
			scanf("%d%d%d", &u, &v, &w);
			if(pre[u] != v && pre[v] != u)	sum += ans;
			else	sum += (ans - G[u][v] + min(w, dp[u][v]));
		}
		printf("%.4lf\n", 1.0 * sum / Q);
	}
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值