题目
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;
}