题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=4126
题意
简而言之,就是给你一张图(稠密图),现在有 Q Q Q 次修改,每次依原图更改一条边的权值(只会比原图的大),让你求出每次更改后的最小生成树权值,取这 Q Q Q 次最小生成树权值的平均值
思路
我们先不考虑修改,就是求出原图的最小生成树,由于是稠密图,所以一般用
p
r
i
m
prim
prim 算法,因为其和边没有关系,只在乎点的个数,复杂度
O
(
n
2
)
O(n^2)
O(n2)。在求解的过程中,我们需要保存最小生成树上的边,并建图(后面要用),设求得的最小生成树总权值为
s
u
m
sum
sum 。
现在考虑修改的影响,显然有两种情况:
- 要修改的边不在最小生成树上
- 要修改的边在最小生成树上
对于第一种情况,由于题目说了,修改的边只会比原来大,所以即使修改后最小生成树也还是没变,所以此组修改对总权值的贡献为
s
u
m
sum
sum。
对于第二种情况,我们需要找这条边的最小替换边。不妨设要修改的边为
(
u
,
v
)
(u,v)
(u,v)、修改成的权值为
w
2
w_2
w2,最小生成树上这条边的权值为
w
1
w_1
w1,现在我们如果把最小生成树上的这条边拆掉,那么最小生成树就被拆成了两棵生成树,我们需要找的就是连接这两棵生成树的最小边(设其权值为
w
w
w)。则此组修改对总权值的贡献为
s
u
m
−
w
1
+
m
i
n
(
w
,
w
2
)
sum-w_1+min(w,w_2)
sum−w1+min(w,w2)。怎么做呢? ----树形dp
由于
n
n
n 不大,所以考虑
n
2
n^2
n2 做法,对于最小生成树上的每一条边,我们都尝试找出能替换它的最小边。
定义
d
p
[
u
]
[
v
]
dp[u][v]
dp[u][v]:顶点
u
u
u 和
v
v
v 分别在两棵生成树上时,将其联通的最小代价。
我们枚举根,将根和其它任意结点所在子树看成两棵生成树,我们一遍
d
f
s
dfs
dfs 回溯来更新当前根下,任意子树与根所在生成树联通的最小替换边,这样我们把每个点都作为根跑一次,就能将所有最小生成树的边的最小替换边找出来。具体转移看代码注释。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#define ft first
#define sd second
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int maxn=3e3+7;
const int inf=0x3f3f3f3f;
int n,m;
int ans;//最小生成树权值
int ma[maxn][maxn];//原图邻接矩阵
int pre[maxn];//pre[i]存i点的前驱
int dis[maxn];//dis[i]存点i和点pre[i]的距离
int dp[maxn][maxn];//dp[u][v]存边(u,v)的最小替换边权值
bool vis[maxn],is[maxn][maxn];
vector<int> g[maxn];//最小生成树邻接表
void prim(){//求原图最小生成树
ans=0;
for(int i=1;i<=n;i++)dis[i]=ma[1][i],pre[i]=1,vis[i]=0;
vis[1]=1,pre[1]=-1;
for(int i=1;i<n;i++){
int mi=inf,p=0;
for(int j=1;j<=n;j++){
if(!vis[j]&&dis[j]<mi){
mi=dis[j];p=j;
}
}
ans+=mi;
if(pre[p]!=-1){
g[pre[p]].pb(p);
g[p].pb(pre[p]);
is[pre[p]][p]=is[p][pre[p]]=1;
}
vis[p]=1;
for(int j=1;j<=n;j++){
if(!vis[j]&&ma[p][j]<dis[j]){
dis[j]=ma[p][j];
pre[j]=p;
}
}
}
}
int dfs(int u,int fa,int root){
int mi=inf;
for(int i=0;i<(int)g[u].size();i++){
int v=g[u][i];
if(v==fa)continue;
//tmp为u和v点分属两个集合相连通的最小替换边的权值
//这个权值是u为根子树中某个点到根的边权(不是树上的边)
//只通过到根的距离来更新的话可能不是最优的
//所以我们才要换根,来找到最优的那个距离
int tmp=dfs(v,u,root);
dp[u][v]=dp[v][u]=min(dp[u][v],tmp);
mi=min(tmp,mi);
}
//如果fa是root,边(root,u)是最小生成树上的边
//因为我们要找最小生成树外的边,所以不可选
if(fa!=root)mi=min(mi,ma[root][u]);
return mi;
}
int main() {
while(~scanf("%d%d",&n,&m)){
if(!n&&!m)break;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
ma[i][j]=dp[i][j]=inf;
is[i][j]=0;
}
g[i].clear();
}
for(int i=0;i<m;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
++u,++v;//范围调整为:[1,n]
ma[u][v]=ma[v][u]=w;
}
prim();
for(int i=1;i<=n;i++)dfs(i,0,i);
int q;scanf("%d",&q);
double sum=0;
for(int i=0;i<q;i++){
int u,v,w;scanf("%d%d%d",&u,&v,&w);
++u,++v;
if(is[u][v]){//是最小生成树上的边
sum+=ans-ma[u][v]+min(w,dp[u][v]);
}
else sum+=ans;
}
printf("%.4f\n",sum/q);
}
return 0;
}