hdu4126 Genghis Khan the Conqueror(最小生成树+树形dp)

题目

n(n<=3e3)个点,m(0<=m<=C(n,2))条边的无向图,

第i条边连接xi和yi,代价为ci(0<=ci<=1e7),

保证图连通且无重边,保证所有边代价之和<=1e9

以下,有q(q<=1e4)种互斥的破坏的可能,

第j种会把连接xj和yj之间的边,调升至cj(保证cj比原来大,且cj<=1e7)

最终会等概率地选择其中一种进行破坏,

问将n个点连通在一起的期望代价和

思路来源

https://www.cnblogs.com/samhx/p/HDU-4126.html

题解

翻了好几篇题解都没懂,最后翻到了这位博主的博客,

这位博主也翻了好几篇才懂,最终讲的也很清楚明白,树形dp还是博大精深啊orz

 

对q种情况,每一种情况求其对应的最小生成树代价,最终除以q即为答案

①如果不在最小生成树上,生成树代价不变,

②如果这条边在最小生成树上,相当于将原树拆成两棵树,

最小生成树代价为两棵生成树的代价,加上这两棵树外一条最小边(注意破坏边边权已被放大)的代价

 

第一部分是最小生成树,先套kruskal,然后要把最小生成树实际建出来,

对这棵最小生成树搞树形dp,dp[u][v]代表u和v通过不在生成树上的边达到连通的最小代价

 

具体实现时,通过枚举根rt,根rt到叶节点之间相通只能通过直连更新,

此时,认为最小生成树是一棵rt为根的树,叶节点是一棵树,通过叶节点到rt的距离更新

回溯非叶节点u->v时,认为u已经和根rt相通,v已经得到了最小的树外边代价,dis[u][v]与tmp取小

再向上回溯时,把u和根rt相通的代价也考虑进去,不断取小,即可得出树上每对相邻点的最小替换代价

 

实质上,是对于u-v这条边(u是近根节点),认为u已经和根rt相通,

通过v这棵子树与rt相通的最小代价,来更新和u相通的最小代价

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<numeric>
using namespace std;
typedef long long ll;
const int N=3005,M=N*N,INF=0x3f3f3f3f;
struct rd{int u,v,w;}e[M];
bool operator<(rd a,rd b){return a.w<b.w;}
struct edge{int v,w,nex;}f[N*2];
int n,m,q,par[N],head[N],dp[N][N],dis[N][N],cnt; 
bool used[N][N];
ll ans,sum;
void init()
{
	memset(used,false,sizeof used);
	memset(head,0,sizeof head);
	memset(dis,INF,sizeof dis);
	memset(dp,INF,sizeof dp);
	ans=sum=cnt=0;
} 
int find(int x)
{
	return par[x]==x?x:par[x]=find(par[x]);
}
void add(int u,int v,int w)
{
	f[++cnt]=edge{v,w,head[u]};
	head[u]=cnt;
}
void kruskal()
{
	iota(par,par+n,0);//填充0-(n-1)的连续值 
	sort(e+1,e+m+1);
	int tot=0,u,v,w;
	for(int i=1;i<=m;++i)
	{
		u=find(e[i].u),v=find(e[i].v),w=e[i].w;
		if(u!=v)
		{
			par[u]=v;sum+=w;tot++;
			add(e[i].u,e[i].v,w);
			add(e[i].v,e[i].u,w);
			used[e[i].u][e[i].v]=used[e[i].v][e[i].u]=1;
		}
		if(tot==n-1)break;
	}
}
//通过枚举树根rt rt与i相通 v通过树外边与rt相通 从而与i相通 
int dfs(int u,int fa,int rt) 
{
	int res=INF;
	for(int i=head[u];i;i=f[i].nex)
	{
		int v=f[i].v;
		if(v==fa)continue;
		int tmp=dfs(v,u,rt);//v这棵子树 切断了rt到v的路径后 通过树外的一条边 到rt的最小距离 
		dp[u][v]=min(dp[u][v],tmp);
		dp[v][u]=dp[u][v];
		res=min(res,tmp);//u这棵子树 间接通过所有v 到rt的最小距离  
	} 
	if(fa!=rt&&fa!=-1)res=min(res,dis[rt][u]);//u本身 通过树外一条边 到达rt 判断与rt不直连即可
	//fa!=rt时还有一种情况 刚进入dfs(rt,-1,rt)时 应判fa!=-1 但不影响 因为此时dis[u][u]=INF 
	return res;
}
int main()
{
	int u,v,w;
	while(~scanf("%d%d",&n,&m))
	{
		if(!n&&!m)break;
		init();
		for(int i=1;i<=m;++i)
		{
			scanf("%d%d%d",&u,&v,&w);
			e[i]=rd{u,v,w};
			dis[u][v]=dis[v][u]=w;
		}
		kruskal();
		for(int i=0;i<n;++i)
		dfs(i,-1,i);
		scanf("%d",&q);
		for(int i=1;i<=q;++i)
		{
			scanf("%d%d%d",&u,&v,&w);
			//printf("u:%d v:%d dp:%d\n",u,v,dp[u][v]);
			if(!used[u][v])ans+=sum;
			else ans+=sum-dis[u][v]+min(dp[u][v],w);
		}
		printf("%.4lf\n",1.0*ans/q);
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值