題目鏈接: 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;
}