HDU 4126 Genghis Khan the Conqueror 最小生成树+树形dp
题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=4126
Genghis Khan the Conqueror
Memory Limit: 327680/327680 K (Java/Others)
样例输出
6.0000
题意
给你一个无相图,要你求最小生成树,现在有q个查询,每个查询会改变一条边的权值,然后再改回去,要你求出每种情况下的最小生成树,最后求一下平均。
题解
1、如果改变的边不在我们一开始求的mst上,那么答案就是mst。
2、否则,把指定的树边删了,我们得到两个顶点集,那么代替原先那条边的一定是这两个集合的最短距离。所以我们只要处理出best[u][v](既u所在的集合和v所在的集合的最短距离)就能处理第2种情况了。做法是两次树dp,具体看代码。dp[u][v]:顶点u到集合v的最短距离。
best[u][v]:集合u到集合v的最短距离。
代码
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<ctime>
#include<vector>
#include<cstdio>
#include<string>
#include<bitset>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
#define X first
#define Y second
#define mkp make_pair
#define lson (o<<1)
#define rson ((o<<1)|1)
#define mid (l+(r-l)/2)
#define sz() size()
#define pb(v) push_back(v)
#define all(o) (o).begin(),(o).end()
#define clr(a,v) memset(a,v,sizeof(a))
#define bug(a) cout<<#a<<" = "<<a<<endl
#define rep(i,a,b) for(int i=a;i<(b);i++)
#define scf scanf
#define prf printf
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> PII;
typedef vector<pair<int,int> > VPII;
const int INF=0x3f3f3f3f;
const LL INFL=0x3f3f3f3f3f3f3f3fLL;
const double eps=1e-8;
const double PI = acos(-1.0);
//start----------------------------------------------------------------------
const int maxn=3030;
struct Edge {
int u,v,w;
Edge(int u,int v,int w):u(u),v(v),w(w) {}
Edge() {}
bool operator < (const Edge& tmp) const {
return w<tmp.w;
}
} egs[maxn*maxn];
int n,m;
int G[maxn][maxn],dp[maxn][maxn],best[maxn][maxn];
bool used[maxn][maxn];
VPII tre[maxn];
int fa[maxn];
int find(int x) {
return fa[x]=fa[x]==x?x:find(fa[x]);
}
void dfs(int u,int fa,int rt) {
if(u!=rt&&!used[u][rt]) dp[rt][u]=min(dp[rt][u],G[rt][u]);
rep(i,0,tre[u].sz()) {
int v=tre[u][i].X;
if(v==fa) continue;
dfs(v,u,rt);
dp[rt][u]=min(dp[rt][u],dp[rt][v]);
}
}
int dfs2(int u,int fa,int rt) {
if(best[u][rt]<100000000) return best[u][rt];
best[u][rt]=min(best[u][rt],dp[u][rt]);
for(int i=0; i<tre[u].sz(); i++) {
int v=tre[u][i].X;
if(v==fa) continue;
dfs2(v,u,rt);
best[u][rt]=min(best[u][rt],best[v][rt]);
}
return best[u][rt];
}
///最小生成树
double kruskal() {
sort(egs,egs+m);
double mst=0;
for(int i=0; i<m; i++) {
int u=egs[i].u,v=egs[i].v,w=egs[i].w;
int pu=find(u);
int pv=find(v);
if(pu!=pv) {
fa[pv]=pu;
tre[u].pb(mkp(v,w));
tre[v].pb(mkp(u,w));
used[u][v]=used[v][u]=1;
mst+=w;
}
}
return mst;
}
void init() {
clr(G,0x3f);
clr(dp,0x3f);
clr(best,0x3f);
clr(used,0);
for(int i=0; i<n; i++) fa[i]=i,tre[i].clear();
}
int main() {
while(scf("%d%d",&n,&m)==2&&n) {
init();
for(int i=0; i<m; i++) {
int u,v,w;
scf("%d%d%d",&u,&v,&w);
G[u][v]=G[v][u]=w;
egs[i]=Edge(u,v,w);
}
double mst=kruskal();
///树形dp,dp[u][v]表示u到以v为根的子树的最短距离,既点到集合的距离(以u为根开始遍历,且只用非树边更新)
for(int i=0; i<n; i++) dfs(i,-1,i);
///记忆化搜索,best[u][v]表示以u为根的子树和以v为根的子树之间的最短距离,既集合到集合的距离
///对于子树v,我们已经求出了所有的点u到它的最短距离,现在只要遍历所有的u,求出最小值即可。
for(int i=0; i<n; i++) {
for(int j=0; j<tre[i].sz(); j++) {
int v=tre[i][j].X;
best[i][v]=best[v][i]=dfs2(i,v,v);
}
}
int q;
scf("%d",&q);
double ans=0;
rep(i,0,q) {
int u,v,w;
scf("%d%d%d",&u,&v,&w);
if(used[u][v]) {
///在树边上
ans+=mst-G[u][v]+min(w,best[u][v]);
} else {
///不在树边上
ans+=mst;
}
}
prf("%.4lf\n",ans/q);
}
return 0;
}
//end-----------------------------------------------------------------------