HDU - 4126 和 HDU - 4756 基本是一样的题, 前面是后面那道的简化版
这两道题主要涉及的一个问题就是, 每次替换掉图中的一条边, 问此时图的最小生成树是多少..
很明显有个暴力的方法, 但是这样时间爆炸, 我们可以这样想, 先求一次图的最小生成树, 如果替换的边不是树边, 那么此时的图的MST依旧不会变, 如果是树边, 很明显, 我们需要在原图中找一条不是树边, 并且尽量小的边去替换, 这样的答案就可以保证是最优的, 所以我们可以预先处理出所有两点的最佳替换边, 具体过程为我们先求出MST, 然后将每个点作为根进行dfs一遍, 维护dp[u][v],dp[u][v] 代表的是切断u - v这条边后,S集(u所在的集合)到T集(v所在的集合)的最短距离。
分别以图中每一个点为根,假如当前根是root, 则在最小生成树的图中进行深搜,假如一条搜索路径为,
root ->v1->v2->v3->u,到u搜索不下去了.
则我们倒着考虑,切断v3-u,这个边,则u单独成一个集合,其他n-1个点是
一个结合,此时考录root到u的距离,dp[v3][u] = min(dp[v3][u],dis(root,u));
这样我们有了root-u的距离。然后考虑切断v2-v3,则v3,u是一个集合了,其他n-2
个点成了一个结合,我们也可以得到root-v3,则两个集合的最短距离是min(root-v3, root-u, dp[v2][v3]).
然后考虑切断v1 - v2, 则v2, v3, u成了一个集合, 其他n-3个点是一个集合,
两个集合的最短距离是min(root-v2, root-v3, root-u, dp[v1][v2])
如果切断root-v1, 则v1, v2, v3, u 是一个集合, root自己是一个集合,其最短距离是
min(root-v2,root-v3,root-u,dp[v1][v2]), 这时因为root - v1是构成最小生成树的边,
我们不能把它考虑进去. 按照这个规则, 我们以每个节点为根都进行这样的深搜,得到
断掉最小生成树中的边后,两个集合的最短距离,然后暴力枚举删除最小生成树的边来求解
HDU - 4126 AC代码
题意: q次替换图中的一条边, 问此时图的最小生成树是多少, 然后q次为等概率, 求期望是多少.
const int maxn = 3e3+5;
int dp[maxn][maxn], s[maxn][maxn];
vector<int>g[maxn];
int n, m;
struct edge {
int u, v, w;
bool operator < (const edge &_ ) const {
return w < _.w;
}
}e[maxn*maxn];
int fa[maxn], r[maxn];
bool vis[maxn][maxn];
void init() {
for (int i = 1 ; i <= n ; i ++) {
fa[i] = i; r[i] = 1;
g[i].clear();
for (int j = 1 ; j <= n ; j ++) s[i][j] = dp[i][j] = inf;
s[i][i] = dp[i][i] = 0;
}
Fill(vis, 0);
}
int Find(int x) {
return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
bool Un(int x, int y) {
int fx = Find(x);
int fy = Find(y);
if (fx == fy) return false;
if (r[fx] > r[fy]) swap(fx, fy);
fa[fx] = fy;
r[fy] += r[fx];
return true;
}
ll kru() {
int cnt = 0; ll tot = 0;
for (int i = 1 ; i <= m ; i ++) {
if (Un(e[i].u, e[i].v)) {
++ cnt;
g[e[i].u].pb(e[i].v);
g[e[i].v].pb(e[i].u);
vis[e[i].u][e[i].v] = vis[e[i].v][e[i].u] = 1;
tot += e[i].w;
}
if (cnt >= n - 1) break;
}
return tot;
}
int dfs(int u, int fa, int r) {
int mi = inf;
for (auto it : g[u]) {
if (it == fa) continue;
int tmp = dfs(it, u, r);
mi = min(mi, tmp);
dp[it][u] = dp[u][it] = min(dp[it][u], tmp);
}
if (r != fa) mi = min(mi, s[r][u]);
return mi;
}
void solve() {
while(~scanf("%d%d",&n,&m)) {
if (n + m == 0) break; init();
for (int i = 1 ; i <= m ; i ++) {
scanf("%d%d%d",&e[i].u, &e[i].v, &e[i].w);
++e[i].u; ++e[i].v;
s[e[i].u][e[i].v] = s[e[i].v][e[i].u] = e[i].w;
}
sort(e+1, e+1+m);
ll tot = kru(), sum = 0;
for (int i = 1 ; i <= n ; i ++) dfs(i, -1, i);
int q; scanf("%d", &q); int qq = q;
while(q--) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
++u; ++v;
if (!vis[u][v]) sum += tot;
else sum += tot - s[u][v] + min(dp[u][v], w);
}
printf("%.4f\n", 1.0*sum/qq);
}
}
HDU 4756 AC Code
题意: 有n个点, 第一个点为发电站, 其他点为宿舍, 此时的最小生成树为联通所有宿舍的最低花费, 现在问在原先的最小生成树中去掉宿舍之间的某一条边, 问最多的花费是变为多少乐?
const int maxn = 1e3+5;
int fa[maxn], r[maxn];
int n, m;
int head[maxn], cnt ;
struct node {
int to, next; db w;
}e[maxn<<1];
int vis[maxn*maxn], mark[maxn];
void init() {
for (int i = 1 ; i <= n ; i ++) {
fa[i] = i;
r[i] = 1;
} Fill(mark, 0);
Fill(vis, 0); Fill(head, -1); cnt = 0;
}
void add(int u, int v, db w) {
e[cnt] = node{v, head[u], w};
head[u] = cnt ++;
}
int Find(int x) {
return fa[x] == x ? x : fa[x] = Find(fa[x]);
}
bool Un(int x, int y) {
int fx = Find(x);
int fy = Find(y);
if (fx == fy) return false;
if (r[fx] > r[fy]) swap(fx, fy);
fa[fx] = fy;
r[fy] += r[fx];
return true;
}
struct Point {
db x, y;
}p[maxn];
db dis(int i, int j) {
return sqrt((p[i].x-p[j].x) * (p[i].x-p[j].x) + (p[i].y - p[j].y) * (p[i].y - p[j].y));
}
db dp[maxn][maxn], G[maxn][maxn];
struct edge {
int u, v; db w;
bool operator < (const edge& _) const {
return w < _.w;
}
}s[maxn*maxn];
db dfs(int r, int u, int fa) {
db mi = INF;
for (int i = head[u] ; ~i ; i = e[i].next) {
int to = e[i].to;
if (to == fa) continue;
db tmp = dfs(r, to, u);
mi = min(mi, tmp);
dp[u][to] = dp[to][u] = min(dp[u][to], tmp);
}
if (r != fa) mi = min(mi, G[r][u]);
return mi;
}
edge tt[maxn];
void solve()
{
scanf("%d%d", &n, &m); init();
for (int i = 1 ; i <= n ; i ++) {
scanf("%lf%lf", &p[i].x, &p[i].y);
}
int k = 0;
for (int i = 1 ; i <= n ; i ++) {
for (int j = i + 1 ; j <= n ; j ++) {
s[++k] = edge{i, j, dis(i, j)};
G[i][j] = G[j][i] = s[k].w;
dp[i][j] = dp[j][i] = 1.0*INF;
}
dp[i][i] = INF; G[i][i] = 0;
}
sort(s+1, s+1+k);
db tot = 0; int cnt = 0, c = 0;
for (int i = 1 ; i <= k ; i ++) {
if (Un(s[i].u, s[i].v)) {
++ cnt;
tot += s[i].w;
add(s[i].u, s[i].v, s[i].w);
add(s[i].v, s[i].u, s[i].w);
if (s[i].u != 1) tt[++c] = edge{s[i].u, s[i].v, s[i].w};
vis[i] = 1;
}
if (cnt >= n - 1) break;
}
for (int i = 1 ; i <= n ; i ++) {
dfs(i, i, -1);
}
db ans = tot;
for (int i = 1 ; i <= c ; i ++) {
ans = max(ans, tot - tt[i].w + dp[tt[i].u][tt[i].v]);
}
if (ans == INF) printf("%.2f\n", tot*m);
else printf("%.2f\n", ans*m);
}