HDU 4126 Genghis Khan the Conqueror (生成树,并查集)

题意: 给出一幅3000个点的图,有10000次操作: 求将某条边的权值变大后的最小生成树,最后输出10000次操作得到的最小生成树权值的平均值。

解法: 首先,对于一棵最小生成树,如果我们将它的某条边增大,那么存在一棵新的最小生成树与原生成树只有一条边是不同的,并且这条边与那条增大边在同一个环里,并且是与这条增大边同环的原最小非树边。

证明如下: 

由最小生成树的定义,最小生成树是一棵具有最小权值的无向无环图T,那么可知,对于任意一条非T边x,若将x加入树中,必然会形成一个环,并且环中的任意一条树边的权值都不会比x大(若存在比x大的边,那么显然可以通过将其替换成x来得到一棵更小的生成树,显然与T是最小生成树不符)。那么不妨将最小生成树的定义修改为:原图的一个无环的边集合,并且将图中的任意一条集合外的边加入集合中,都将形成环,并且该边的权值不小于环中的最大边权。

那么当我们将一条边x的权值变大时,若它不是树边,则不影响最小生成树。若它是树边,则可以通过用与其成环的小权值非树边s来替换它,以得到更小的生成树T`。我们现在考虑这样替换形成的生成树T`是不是最小的:假设T`不是当前最小生成树,则存在一条非树边s`,能够替换T`中的某条与s`成环的边以得到更小的生成树。考虑这条非树边s`的来源:有两种可能,一种是原最小生成树T的非树边,若该边与T`中的s之外的任意边成环,则等价于s`在T中成环,而由T的定义,T中的边已经是不可被替换的了。另一种情况,若s`是x,而x是用s在T中替换出来的,再用x与T'中某条非s边:d进行替换,等价于s直接替换d,即s能够替换d得到更小的生成树,而s本是T非树边,d是T的树边,则说明T不是最小生成树,与假设矛盾。证毕。

因此对于这题,我们只需要找到与每条树边对应的成环边中最小的那条(称为替换边),与修改后的权值进行比较,即可O(1)得到新的最小生成树。而如何找替换边呢?我们按照Kruskal选边的顺序,当我们遇到一条非树边s时,树中与该边成环的未染色的边的最佳替换边显然就是s了。

存在另外一个问题,染色的过程中,每次都要求出lca,然后检查路径上的未染色边,每次检查可能需要O(n)的时间,总共n^2条边,即有n^2次检查,复杂度O(n^3)承受不起。

注意到,对已染色的边进行遍历是浪费时间,而树上的每个节点都只有一个父亲,那么可以用一个并查集,若该点到其父亲的边已经被染色,则将该点的fa直接指向其父亲,以避免对染色边的多次检查。这样,对每条非树边会扫描一次,对树边也只扫描一次,染色复杂度O(n^2)。

对于每次询问,原边权重x,修改边权重x`,替换边权重c,原最小生成树权和sum,

则新生成树权和

sum` = sum - x + min(x`,c) (该边为原树边)

             sum                           (该边非树边)

/* Created Time: Saturday, November 09, 2013 AM11:37:29 CST */
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long lld;
const int INF = 0x3f3f3f3f;
const int N = 3333;
const int POW = 13;
int n,m;
struct E {
    int u,v,w,next;
    void read() {
        scanf("%d%d%d",&u,&v,&w);
    }
    bool operator < (const E &tt) const {
        return w<tt.w;
    }
}edge[N*N],g[N<<1];
struct Node {
    int fat,id;
}nod[N];
int etot,head[N],fa[N],p[N][13],dep[N],idx,tim;
int mat[N][N],org[N],tak[N];
lld sum;
void add_edge(int u,int v,int w) {
    g[etot].v = v; g[etot].u = u;
    g[etot].w = w; g[etot].next = head[u];
    head[u] = etot ++;
}
int Find(int x) { return fa[x]==x ? x : fa[x] = Find(fa[x]); }
bool merge(int a,int b) {
    a = Find(a),b = Find(b);
    if (a==b) return false;
    fa[a] = b;
    return true;
}
void dfs(int u,int fid,int deep) {
    if (fid!=-1) {
        nod[u].id = idx;
        nod[u].fat = g[fid].u;
        mat[g[fid].u][g[fid].v] = idx;
        mat[g[fid].v][g[fid].u] = idx;
        org[idx] = g[fid].w;
        tak[idx] = INF;
        idx ++;
        p[u][0] = g[fid].u;
    } else {
        nod[u].id = -1;
        nod[u].fat = -1;
    }
    for (int i = 1; i < 13; i ++)
        p[u][i] = p[p[u][i-1]][i-1];
    dep[u] = deep;
    for (int i = head[u]; i != -1; i = g[i].next) {
        int v = g[i].v;
        if (fid!=-1 && g[fid].u==v) continue;
        dfs(v,i,deep+1);
    }
}
int lca(int a,int b) {
    if (dep[a]<dep[b]) swap(a,b);
    int delta = dep[a]-dep[b];
    for (int i = 0; i < POW; i ++) 
        if (delta>>i&1) a = p[a][i];
    if (a!=b) {
        for (int i = POW-1; i >= 0; i --) 
            if (p[a][i]!=p[b][i])
                a = p[a][i],b = p[b][i];
        a = p[a][0];
    }
    return a;
}
void jump(int a,int x,int w) {
    x = Find(x);
    a = Find(a);
    while (a!=x) {
        tak[nod[a].id] = w;
        fa[a] = nod[a].fat;
        a = Find(nod[a].fat);
    }
}
void make_col(int a,int b,int w) {
    int x = lca(a,b);
    jump(a,x,w);
    jump(b,x,w);
}
void work() {
    sort(edge,edge+m);
    for (int i = 0; i < n; i ++) head[i] = -1;
    etot = 0;
    sum = 0;
    tim = 0;
    idx = 0;
    for (int i = 0; i < n; i ++) fa[i] = i;
    memset(p,-1,sizeof(p));
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < n; j ++)
            mat[i][j] = -1;
    for (int i = 0; i < m; i ++)
        if (merge(edge[i].u,edge[i].v)) {
            int u = edge[i].u;
            int v = edge[i].v;
            int w = edge[i].w;
            add_edge(u,v,w);
            add_edge(v,u,w);
            sum += w;
        }
    dfs(0,-1,0);
    for (int i = 0; i < n; i ++) fa[i] = i;
    for (int i = 0; i < m; i ++) {
        int u = edge[i].u;
        int v = edge[i].v;
        int w = edge[i].w;
        if (mat[u][v]!=-1) continue;
        make_col(u,v,w);
    }
}
void sol() {
    int nq;
    scanf("%d",&nq);
    lld fuc = 0;
    for (int i = 0; i < nq; i ++) {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        int id = mat[a][b];
        if (id==-1) {
            fuc += sum;
        } else {
            lld tmp = sum-org[id];
            tmp += min(c,tak[id]);
            fuc += tmp;
        }
    }
    printf("%.4f\n",fuc*1.0/nq);
}
int main() {
    while (~scanf("%d%d",&n,&m),n||m) {
        for (int i = 0; i < m; i ++)
            edge[i].read();
        work();
        sol();
    }
    return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值