题目链接: http://poj.org/problem?id=4006
题意:
你现在有一个 个结点 条边的无向带权连通图,现在你有 个等概率发生的改变,每次会将原图中某一条原先存在的边权变大,但只会改变其中的一条,问你在这样的情况下,图中生成最小生成树权值的期望是多少。
做法:
先对原图中的点做最小生成树,如果改变的边不在这棵树上,那么直接加上最小生成树的值即可。
重要的是在这棵树上的边的改变。如果某一条边发生了改变,那么应该是对原图中这条边两端点所在的集合中再找一条相连的最小边(因为原边变大了)。可以知道的是,最多也只会改变一条边,并且这条边如果出现在原来的树中,一定会形成一个环。
原先我的想法是,用一个“次小边”的概念,是树上每一条边两端的结点除原来的最小值外再求一个次小值。原先不在这个树上的边,可以用来更新这个环上的最多两条链的次小值,在改变这条边的时候,只需要在次小值和变大后的值中取 即可。
但是线段树写的有点麻烦,树链剖分又…emmmm…不熟练。
所以就参考了网上稍微简便一点的方法写,用 表示,树上一条边两边的点集 和 要重新相连的次短边(其实和我原来的概念挺像的,只是大佬的 dp 要方便好多…)。
这个该怎么做呢,因为只有 个点,所以我们完全可以 的来做,对于每一个点都可以把它当做根节点做一次 ,每次从叶子开始将根到这个集合的最小的值记录下来,来更新以这个叶子往上的所有子树。
代码
#include <cstdio>
#include<algorithm>
#include<cmath>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define rep_e(i,u,v) for(int i=head[u],v=to[i];~i;i=nex[i],v=to[i])
using namespace std;
typedef long long ll;
const int maxn=3005;
const int maxm=maxn*2;
const ll inf = 1e16;
struct edge{
int u,v;ll val;
}e[maxn*maxn];
int n,m;
ll dis[maxn][maxn],dp[maxn][maxn],fa[maxn];
int head[maxn],to[maxm],nex[maxm],cnt;
bool mark[maxn][maxn];
int fin(int x){
return fa[x]==x?x:fa[x]=fin(fa[x]);
}
void add(int u,int v){
//printf("from = %d to = %d\n",u,v);
to[cnt]=v;nex[cnt]=head[u];
head[u]=cnt++;
}
void addEdge(int u,int v,ll val,int id){
e[id].u=u,e[id].v=v,e[id].val=val;
dis[u][v]=dis[v][u]=val;
}
void init(){
rep(i,1,n) {
head[i]=-1; fa[i]=i;
rep(j,1,n) {
dis[i][j]=inf; dp[i][j]=inf;
mark[i][j]=false;
}
}
cnt=0;
}
bool cmp(edge a,edge b){
return a.val<b.val;
}
ll Kru(){
sort(e+1,e+1+m,cmp);
ll ret=0;
rep(i,1,m){
int u=e[i].u,v=e[i].v;
int fu=fin(u),fv=fin(v);
if(fu!=fv){
add(u,v); add(v,u);
ret+=e[i].val;
mark[u][v]=mark[v][u]=true;
fa[fu]=fv;
}
}
return ret;
}
ll dfs(int u,int f,int rt){
ll ret=inf;
rep_e(i,u,v){
if(v==f) continue;
ll nex=dfs(v,u,rt);
dp[u][v]=dp[v][u] =min(dp[u][v],nex);
ret=min(nex,ret);
}
if(f!=rt) ret=min(dis[rt][u],ret);
return ret;
}
int main(){
while(~scanf("%d%d",&n,&m)){
if(n==0&&m==0) break;
init();
rep(i,1,m){
int x,y; ll val; scanf("%d%d%lld",&x,&y,&val); x++,y++;
addEdge(x,y,val,i);
}
ll Min=Kru();
for(int i=1;i<=n;i++) dfs(i,-1,i);
int q; scanf("%d",&q);
double ans=0;
rep(i,1,q){
int x,y; ll tv; scanf("%d%d%lld",&x,&y,&tv); x++,y++;
if(!mark[x][y]) ans=ans+Min;
else{
ans=ans+Min-dis[x][y]+min(tv,dp[x][y]);
}
}
ans=(double)ans*1.0/q;
printf("%.4f\n",ans);
}
return 0;
}