zoukankan      html  css  js  c++  java
  • 生成树

    最小生成树

    稀疏图用prim+heap,稠密图用kruscal,一般给的是稀疏图

    prim+heap模板:

    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <string>
    #include <cmath>
    #include <iomanip>
    #include <algorithm>
    
    using namespace std;
    const int N = 2000+10;
    const int inf = 0x3f3f3f3f;
    int n, m;
    double dis[N];
    int vis[N];
    struct E {
        int to, next;
        double w;
    }edge[N*N];
    
    int tot, head[N];
    
    inline void add_edge(int u, int v, double w) {
        edge[tot].to = v;
        edge[tot].w = w;
        edge[tot].next = head[u];
        head[u] = tot++;
    }
    
    bool Prim_heap()
    {
        int st = 1;
        for (int i = 0; i <= n; ++ i) dis[i] = inf;
        memset(vis, 0, sizeof(vis));
        priority_queue<pair<double,int> > q;
        dis[st] = 0;
        q.push({0,st});
        while (!q.empty()) {
            int u = q.top().second;
            q.pop();
            vis[u] = 1;
            for (int i = head[u]; ~i; i = edge[i].next) {
                int v = edge[i].to;
                double w = edge[i].w;
                if (!vis[v] && dis[v] > w) {
                    dis[v] = w;
                    q.push(make_pair(-dis[v], v));
                    //优先队列大根堆变小根堆小 骚操作:只需一个‘-’号;
                }
            }
        }
        for (int i = 1; i <= n; ++ i)
            if (dis[i] == inf)
                return 0;  //判断是否不存在最小生成树
        return 1;
    }
    
    void init()
    {
        tot = 0;
        //memset(head, -1, sizeof (head));
        for (int i = 0; i <= n; ++ i) head[i] = -1;
    }
    
    int main()
    {
        int T;
        scanf("%d", &T);
        while (T--) {
            scanf("%d", &n);
            init();
            int x[n+1], y[n+1];
            for (int i = 1; i <= n; ++ i)
                scanf("%d %d", &x[i], &y[i]);
            for (int i = 1; i <= n; ++ i) {
                for (int j = 1; j <= n; ++ j) {
                    if (i == j) continue;
                    double temp = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
                    if (temp < 10 || temp > 1000) continue;
                    add_edge(i, j, temp*100);
                }
            }
            if (Prim_heap()) {
                double ans = 0;
                for (int i = 1; i <= n; ++ i)
                    ans += dis[i];//, cout << dis[i] << endl;
                cout << fixed << setprecision(1) << ans << endl;
            } else cout << "oh!" << endl;
    
        }
        return 0;
    }
    View Code

    kruscal:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <vector>
    #include <cmath>
    using namespace std;
    const int N = 100000+10, M = 200000+10;
    
    struct Edge{
        int u,v;
        double w;
        bool operator < (const Edge &E)const {
            return w<E.w;
        }
    }edge[M];
    
    int fa[N];
    int n,m;
    
    int _find(int x)
    {
        if(fa[x]==x)return x;
        else return fa[x]=_find(fa[x]);
    }
    
    int main()
    {
        scanf("%d", &n);
        int cnt=0, ans=0;
        int x[n+1], y[n+1];
        int k;
    
        for (int i = 1; i <= n; i++) fa[i] = i;
    
        for (int i = 1; i <= n; i++)
            scanf("%d %d", &x[i], &y[i]);
        scanf("%d", &k);
        for (int i = 1,u,v; i <= k; ++ i) {
            scanf("%d %d", &u, &v);
            fa[_find(u)] = _find(v);
        }
        for (int i = 1; i < n; ++ i) {
            for (int j = i+1; j <= n; ++ j) {
                ++cnt, edge[cnt].u = i, edge[cnt].v = j,
                edge[cnt].w = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
            }
        }
        sort(edge+1, edge+cnt+1);
        vector<pair<int, int> > rec;
        for (int i = 1; i <= cnt; i++) {
            int u = _find(edge[i].u);
            int v = _find(edge[i].v);
            //double w = edge[i].w;
            if (u != v) {
                cnt ++;
                fa[u] = v;
                rec.push_back({edge[i].u, edge[i].v});
            }
        }
        for (int i = 0; i < rec.size(); ++ i) {
            cout << rec[i].first << " " << rec[i].second << endl;
        }
    
        return 0;
    }
    View Code

    次小生成树

    (非严格 / 严格)Kruscal + 倍增LCA  O(nlogn+ mlogm)

    顺便存个倍增LCA

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 500000+10;
    struct Edge{
        int to, next;
        int w;
    } edge[maxn*2];
    
    int head[maxn], cnt;
    int depth[maxn], fa[maxn][19], lg[maxn];
    
    inline void Add_Edge(int u, int v, int w){
        edge[cnt].next = head[u];
        edge[cnt].to = v;
        edge[cnt].w = w;
        head[u] = cnt++;
    }
    
    void dfs(int now, int fath) {  //now表示当前节点,fath表示它的父亲节点
        fa[now][0] = fath; depth[now] = depth[fath] + 1;
        for(int i = 1; i <= lg[depth[now]]; ++i)
            fa[now][i] = fa[fa[now][i-1]][i-1]; //这个转移可以说是算法的核心之一
                                        //意思是now的2^i祖先等于now的2^(i-1)祖先的2^(i-1)祖先
                                            //2^i = 2^(i-1) + 2^(i-1)
        for(int i = head[now]; ~i; i = edge[i].next)
            if(edge[i].to != fath) dfs(edge[i].to, now);
    }
    
    int LCA(int x, int y) {
        if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
            swap(x, y);
        while(depth[x] > depth[y])
            x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
        if(x == y)  //如果x是y的祖先,那他们的LCA肯定就是x了
            return x;
        for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
            if(fa[x][k] != fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
                x = fa[x][k], y = fa[y][k];
        return fa[x][0];  //返回父节点
    }
    
    void init() {
        memset(head, -1, sizeof(head));
        cnt = 0;
    }
    
    int main() {
        int n, m, s; scanf("%d%d%d", &n, &m, &s);
        init();
        for(int i = 1; i <= n-1; ++i) {
            int x, y; scanf("%d%d", &x, &y);
            Add_Edge(x, y, 0), Add_Edge(y, x, 0);
        }
        for(int i = 1; i <= n; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
            lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
        dfs(s, 0);
        for(int i = 1; i <= m; ++i) {
            int x, y; scanf("%d%d",&x, &y);
            printf("%d\n", LCA(x, y));
        }
        return 0;
    }
    View Code

     严格次小生成树 题目链接:P4180 [BJWC2010]严格次小生成树

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll linf = 2147483647000000;
    const int inf = 0x3f3f3f3f;
    const int maxn = 100000+10;
    const int maxm = 400000+10;
    struct Edge{
        int to, next;
        ll w;
    } edge[maxn*2];
    
    struct Tree{
        int to, from;
        ll w;
        bool operator< (const Tree& tt) {return w < tt.w;}
    } tree[maxm];
    
    int n, m;
    int head[maxn], cnt;
    int depth[maxn], fa[maxn][19], lg[maxn], fat[maxn]; //fa用于lca查询, fat用于并查集
    ll dis1[maxn][19], dis2[maxn][19];
    int used[maxn];
    
    inline ll read()
    {
        int x=0;
        char ch=getchar();
        while(ch<'0'||ch>'9')ch=getchar();
        while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
        return x;
    }
    
    inline void Add_Edge(int u, int v, ll w){
        edge[cnt].next = head[u], edge[cnt].to = v, edge[cnt].w = w;
        head[u] = cnt++;
    //    edge[cnt].next = head[v], edge[cnt].to = u, edge[cnt].w = w;
    //    head[v] = cnt++;
    }
    
    //因为DFS可能会爆栈。我就没用了
    
    //void dfs(int now, int fath) {  //now表示当前节点,fath表示它的父亲节点
    //    fa[now][0] = fath; depth[now] = depth[fath] + 1;
    //    for(int i = 1; i <= lg[depth[now]]; ++i)
    //        fa[now][i] = fa[fa[now][i-1]][i-1]; //这个转移可以说是算法的核心之一
    //                                    //意思是now的2^i祖先等于now的2^(i-1)祖先的2^(i-1)祖先
    //                                        //2^i = 2^(i-1) + 2^(i-1)
    //    for(int i = head[now]; ~i; i = edge[i].next)
    //        if(edge[i].to != fath) dfs(edge[i].to, now);
    //}
    
    void bfs() {
        queue<int> q; q.push(1), depth[1] = 1;
        while (!q.empty()) {
            int fath = q.front(); q.pop();
            for(int i = head[fath]; ~i; i = edge[i].next) {
                int now = edge[i].to;
                if (depth[now]) continue;
                fa[now][0] = fath, depth[now] = depth[fath] + 1;
                dis1[now][0] = edge[i].w, dis2[now][0] = -linf;
                q.push(now);
                for(int j = 1; j <= lg[depth[now]]; ++j) {
                    fa[now][j] = fa[fa[now][j-1]][j-1];
                    //最大值肯定是max不用管
                    dis1[now][j] = max(dis1[now][j-1], dis1[fa[now][j-1]][j-1]);
                    //这可真像树形DP,而我树形DP老菜了。。
                    //如果这一次的权值与最大权值相同,那么次大值不用更新,直接次大取大即可
                    if (dis1[now][j-1] == dis1[fa[now][j-1]][j-1])
                        dis2[now][j] = max(dis2[now][j-1], dis2[fa[now][j-1]][j-1]);
                    //如果这一次的权值与比最大权值还大,那么就在更新前的最大和次大值取大
                    if (dis1[now][j-1] > dis1[fa[now][j-1]][j-1])
                        dis2[now][j] = max(dis2[now][j-1], dis1[fa[now][j-1]][j-1]);
                    //如果这一次的权值比最大权值小,那么就在这次的权值和次大值取大
                    if (dis1[now][j-1] < dis1[fa[now][j-1]][j-1])
                        dis2[now][j] = max(dis1[now][j-1],dis2[fa[now][j-1]][j-1]);
                }
            }
        }
    }
    
    int LCA(int x, int y) {
        if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
            swap(x, y);
        while(depth[x] > depth[y])
            x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
        if(x == y)  //如果x是y的祖先,那他们的LCA肯定就是x了
            return x;
        for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
            if(fa[x][k] != fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
                x = fa[x][k], y = fa[y][k];
        return fa[x][0];  //返回父节点
    }
    
    int _find(int x) {return fat[x]==x?x:fat[x]=_find(fat[x]);}
    
    ll Kruscal() {
        ll sum = 0;
        sort(tree+1, tree+m+1);
        for (int i = 1; i <= n; ++ i) fat[i] = i;
        for (int i = 1; i <= m; ++ i) {
            int u = tree[i].from, v = tree[i].to;
            ll w = tree[i].w;
            int fx = _find(u), fy = _find(v);
            if (fx != fy) {
                used[i] = 1, sum += tree[i].w, fat[fx] = fy;
                Add_Edge(u, v, w), Add_Edge(v, u, w);
            }
        }
        return sum;
    }
    
    void init() {
        memset(head, -1, sizeof(head));
        memset(used, 0, sizeof(used));
        memset(depth, 0, sizeof(depth));
        cnt = 0;
        memset(dis1, 0, sizeof(dis1));
        memset(dis2, 0, sizeof(dis2));
        memset(fa, 0, sizeof(fa));
    }
    
    //计算u到v路径上的最大值跟w的差值
    ll cal(int u, int v, ll w) {
        ll val1=0, val2=0;
        for (int i = lg[depth[u]]; i >= 0; -- i) {
            //倍增找最大
            if (depth[fa[u][i]] >= depth[v]) {
                if (dis1[u][i] > val1) {
                    val2 = val1;
                    val1 = dis1[u][i];
                }
                val2  = max(val2, dis2[u][i]);
                u = fa[u][i];
            }
        }
        if (val1 != w) return w - val1;
        return w - val2;
    }
    
    ll solve() {
        ll base = Kruscal();
        ll ans = linf;
        bfs();
        for (int i = 1; i <= m; ++ i) {
            if (used[i]) continue;
            int u = tree[i].from, v = tree[i].to;
            int lca = LCA(u, v);
            ll w = tree[i].w;
            ans = min(ans, min(base+cal(u, lca, w), base+cal(v, lca, w)));
        }
        return ans;
    }
    
    int main() {
        for(int i = 1; i < maxn; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
            lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
        scanf("%d%d", &n, &m);
        init();
        for(int i = 1; i <= m; ++i)
            scanf("%d%d%lld", &tree[i].from, &tree[i].to, &tree[i].w);
        cout << solve() << endl;
        return 0;
    }
    View Code

    非严格次小生成树 给个板子题吧:K - The Unique MST  POJ - 1679

    #include <cstring>
    #include <iostream>
    #include <queue>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const ll linf = 2147483647000000;
    const int inf = 0x3f3f3f3f;
    const int maxn = 1000+10;
    const int maxm = 40000+10;
    struct Edge{
        int to, next;
        ll w;
    } edge[maxn*2];
    
    struct Tree{
        int to, from;
        ll w;
        bool operator< (const Tree& tt) const {return w < tt.w;}
    } tree[maxm];
    
    int n, m;
    int head[maxn], cnt;
    int depth[maxn], fa[maxn][19], lg[maxn], fat[maxn]; //fa用于lca查询, fat用于并查集
    ll dis1[maxn][19];
    int used[maxn];
    
    inline ll read()
    {
        int x=0;
        char ch=getchar();
        while(ch<'0'||ch>'9')ch=getchar();
        while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
        return x;
    }
    
    inline void Add_Edge(int u, int v, ll w){
        edge[cnt].next = head[u], edge[cnt].to = v, edge[cnt].w = w;
        head[u] = cnt++;
    //    edge[cnt].next = head[v], edge[cnt].to = u, edge[cnt].w = w;
    //    head[v] = cnt++;
    }
    
    //因为DFS可能会爆栈。我就没用了
    
    //void dfs(int now, int fath) {  //now表示当前节点,fath表示它的父亲节点
    //    fa[now][0] = fath; depth[now] = depth[fath] + 1;
    //    for(int i = 1; i <= lg[depth[now]]; ++i)
    //        fa[now][i] = fa[fa[now][i-1]][i-1]; //这个转移可以说是算法的核心之一
    //                                    //意思是now的2^i祖先等于now的2^(i-1)祖先的2^(i-1)祖先
    //                                        //2^i = 2^(i-1) + 2^(i-1)
    //    for(int i = head[now]; ~i; i = edge[i].next)
    //        if(edge[i].to != fath) dfs(edge[i].to, now);
    //}
    
    void bfs() {
        queue<int> q; q.push(1), depth[1] = 1;
        while (!q.empty()) {
            int fath = q.front(); q.pop();
            for(int i = head[fath]; ~i; i = edge[i].next) {
                int now = edge[i].to;
                if (depth[now]) continue;
                fa[now][0] = fath, depth[now] = depth[fath] + 1;
                dis1[now][0] = edge[i].w;
                q.push(now);
                for(int j = 1; j <= lg[depth[now]]; ++j) {
                    fa[now][j] = fa[fa[now][j-1]][j-1];
                    //最大值肯定是max不用管
                    dis1[now][j] = max(dis1[now][j-1], dis1[fa[now][j-1]][j-1]);
                }
            }
        }
    }
    
    int LCA(int x, int y) {
        if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
            swap(x, y);
        while(depth[x] > depth[y])
            x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
        if(x == y)  //如果x是y的祖先,那他们的LCA肯定就是x了
            return x;
        for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
            if(fa[x][k] != fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
                x = fa[x][k], y = fa[y][k];
        return fa[x][0];  //返回父节点
    }
    
    int _find(int x) {return fat[x]==x?x:fat[x]=_find(fat[x]);}
    
    ll Kruscal() {
        ll sum = 0;
        sort(tree+1, tree+m+1);
        for (int i = 1; i <= n; ++ i) fat[i] = i;
        for (int i = 1; i <= m; ++ i) {
            int u = tree[i].from, v = tree[i].to;
            ll w = tree[i].w;
            int fx = _find(u), fy = _find(v);
            if (fx != fy) {
                used[i] = 1, sum += tree[i].w, fat[fx] = fy;
                Add_Edge(u, v, w), Add_Edge(v, u, w);
            }
        }
        return sum;
    }
    
    
    //(非严格)计算u到v路径上的最大值跟w的差值
    ll cal2(int u, int v) {
        ll val=0;
        for (int i = lg[depth[u]]; i >= 0; -- i) {
            //倍增找最大
            if (depth[fa[u][i]] >= depth[v]) {
                val  = max(val, dis1[u][i]);
                u = fa[u][i];
            }
        }
        return val;
    }
    
    ll solve(ll base) {
        ll ans = linf;
        bfs();
        for (int i = 1; i <= m; ++ i) {
            if (used[i]) continue;
            int u = tree[i].from, v = tree[i].to;
            int lca = LCA(u, v);
            ll w = tree[i].w;
            ll maxw = max(cal2(u, lca), cal2(v, lca));
            ans = min(ans, base+w-maxw);
        }
        return ans;
    }
    
    void init() {
        memset(head, -1, sizeof(head));
        memset(used, 0, sizeof(used));
        memset(depth, 0, sizeof(depth));
        cnt = 0;
        memset(dis1, 0, sizeof(dis1));
        memset(fa, 0, sizeof(fa));
    
    }
    
    int main() {
        int T;
        scanf("%d", &T);
        for(int i = 1; i < maxn; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
            lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
        while (T--) {
            scanf("%d%d", &n, &m);
            init();
            for(int i = 1; i <= m; ++i)
                scanf("%d%d%lld", &tree[i].from, &tree[i].to, &tree[i].w);
            ll base = Kruscal();
            ll ans = solve(base);
            //cout << ans << " " << base << endl;
            if (ans > base) cout << base << endl;
            else cout << "Not Unique!" << endl;
        }
    
        return 0;
    }
    View Code

    B - Qin Shi Huang's National Road System  HDU - 4081

    这道题目比较毒瘤吧。虽然就是考一个很简单的非严格次小生成树的点。

    只需要枚举每一条边,之后如果这条边在MST上那么直接边两端val相加除以mst-w即可

    否则就是将u和v的路径上最大的那条边代替w

    我采用上面的方式一直WA,改用rec记录最大值之后就A了。真是离谱

    //#include <bits/stdc++.h>
    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    using namespace std;
    typedef long long ll;
    const ll linf = 2147483647000000;
    const int inf = 0x3f3f3f3f;
    const int maxn = 1000+10;
    const int maxm = maxn*maxn+10;
    struct Edge{
        int to, next;
        double w;
    } edge[maxn*2];
    
    struct Tree{
        int to, from;
        double w;
        bool operator< (const Tree& tt) {return w<tt.w;}
    } tree[maxm];
    
    int n, m;
    int head[maxn], cnt;
    int depth[maxn], fa[maxn][19], lg[maxn], fat[maxn]; //fa用于lca查询, fat用于并查集
    double dis1[maxn][19];
    int used[maxn];
    double xx[maxn], yy[maxn], val[maxn];
    double rec[maxn][maxn];
    int cntt;
    
    inline void Add_Edge(int u, int v, double w){
        edge[cnt].next = head[u], edge[cnt].to = v, edge[cnt].w = w;
        head[u] = cnt++;
    }
    
    void bfs() {
        queue<int> q; q.push(1), depth[1] = 1;
        while (!q.empty()) {
            int fath = q.front(); q.pop();
            for(int i = head[fath]; ~i; i = edge[i].next) {
                int now = edge[i].to;
                if (depth[now]) continue;
                fa[now][0] = fath, depth[now] = depth[fath] + 1;
                dis1[now][0] = edge[i].w;
                q.push(now);
                for(int j = 1; j <= lg[depth[now]]; ++j) {
                    fa[now][j] = fa[fa[now][j-1]][j-1];
                    dis1[now][j] = max(dis1[now][j-1], dis1[fa[now][j-1]][j-1]);
                }
            }
        }
    }
    
    int LCA(int x, int y) {
        if(depth[x] < depth[y]) //用数学语言来说就是:不妨设x的深度 >= y的深度
            swap(x, y);
        while(depth[x] > depth[y])
            x = fa[x][lg[depth[x]-depth[y]] - 1]; //先跳到同一深度
        if(x == y)  //如果x是y的祖先,那他们的LCA肯定就是x了
            return x;
        for(int k = lg[depth[x]] - 1; k >= 0; --k) //不断向上跳(lg就是之前说的常数优化)
            if(fa[x][k] != fa[y][k])  //因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
                x = fa[x][k], y = fa[y][k];
        return fa[x][0];  //返回父节点
    }
    
    int _find(int x) {return fat[x]==x?x:fat[x]=_find(fat[x]);}
    
    double Kruscal() {
        double sum = 0;
        sort(tree+1, tree+cntt+1);
        for (int i = 1; i <= n; ++ i) fat[i] = i;
        for (int i = 1; i <= cntt; ++ i) {
            int u = tree[i].from, v = tree[i].to;
            double w = tree[i].w;
            int fx = _find(u), fy = _find(v);
            if (fx != fy) {
                used[i] = 1, sum += tree[i].w, fat[fx] = fy;
                Add_Edge(u, v, w), Add_Edge(v, u, w);
            }
        }
        return sum;
    }
    
    void init() {
        memset(head, -1, sizeof(head));
        memset(used, 0, sizeof(used));
        memset(depth, 0, sizeof(depth));
        memset(fa, 0, sizeof(fa));
        memset(dis1, 0, sizeof(dis1));
        cnt = cntt = 0;
    }
    
    //(非严格)计算u到v路径上的最大值跟w的差值
    double cal2(int u, int v) {
        double val=0;
        for (int i = lg[depth[u]]; i >= 0; -- i) {
            //倍增找最大
            if (depth[fa[u][i]] >= depth[v]) {
                val  = max(val, dis1[u][i]);
                u = fa[u][i];
            }
        }
        return val;
    }
    
    double solve(double base) {
        double ans = 0;
        bfs();
        for (int i = 1; i < n; ++ i) {
            for (int j = 1; j <= n; ++ j) {
                int lca = LCA(i, j);
                rec[i][j] = rec[j][i] = max(cal2(i, lca), cal2(j, lca));
            }
        }
        for (int i = 1; i < n; i++) {
            for (int j = i+1; j <= n; j++) {
                double v = base - rec[i][j];
                v = (val[i] + val[j]) / v;
                ans = max(ans, v);
            }
        }
        return ans;
    }
    int main() {
    //    freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取
    //    freopen("out1.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中
        int T; scanf("%d", &T);
        for(int i = 1; i <= maxn; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
            lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
        while (T--) {
            scanf("%d", &n);
            init();
            for(int i = 1; i <= n; ++i)
                scanf("%lf%lf%lf", &xx[i], &yy[i], &val[i]);
            for (int i = 1; i < n; ++ i) {
                for (int j = i+1; j <= n; ++ j) {
                    double w = sqrt((xx[i]-xx[j])*(xx[i]-xx[j])+(yy[i]-yy[j])*(yy[i]-yy[j]));
                    tree[++cntt].from = i, tree[cntt].to = j, tree[cntt].w = w;
                    //cout << i << " " << j << " " << w << endl;
                }
            }
            double base = Kruscal();
            double ans = solve(base);
            printf("%.2f\n", ans);
        }
        return 0;
    }
    View Code

    D - Is There A Second Way Left? UVA - 10462

     这几道非严格最小生成树让我成功意识到了我的初始化不够完善。

    这道题就是判断有没有最小生成树和次小生成树。

    #include <cstring>
    #include <iostream>
    #include <queue>
    #include <cstdio>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    const ll linf = 2147483647000000;
    const int inf = 0x3f3f3f3f;
    const int maxn = 200+20;
    const int maxm = 40000+10;
    struct Edge{
        int to, next;
        ll w;
    } edge[maxn*2];
    
    struct Tree{
        int to, from;
        ll w;
        bool operator< (const Tree& tt) const {return w < tt.w;}
    } tree[maxm];
    
    int n, m;
    int head[maxn], cnt;
    int depth[maxn], fa[maxn][19], lg[maxn], fat[maxn]; //fa用于lca查询, fat用于并查集
    ll dis1[maxn][19];
    int used[maxn];
    int cntt;
    
    inline void Add_Edge(int u, int v, ll w){
        edge[cnt].next = head[u], edge[cnt].to = v, edge[cnt].w = w;
        head[u] = cnt++;
    }
    
    void bfs() {
        queue<int> q; q.push(1), depth[1] = 1;
        while (!q.empty()) {
            int fath = q.front(); q.pop();
            for(int i = head[fath]; ~i; i = edge[i].next) {
                int now = edge[i].to;
                if (depth[now]) continue;
                fa[now][0] = fath, depth[now] = depth[fath] + 1;
                dis1[now][0] = edge[i].w;
                q.push(now);
                for(int j = 1; j <= lg[depth[now]]; ++j) {
                    fa[now][j] = fa[fa[now][j-1]][j-1];
                    dis1[now][j] = max(dis1[now][j-1], dis1[fa[now][j-1]][j-1]);
                }
            }
        }
    }
    
    int LCA(int x, int y) {
        if(depth[x] < depth[y]) swap(x, y);
        while(depth[x] > depth[y]) x = fa[x][lg[depth[x]-depth[y]] - 1];
        if(x == y) return x;
        for(int k = lg[depth[x]] - 1; k >= 0; --k)
            if(fa[x][k] != fa[y][k])
                x = fa[x][k], y = fa[y][k];
        return fa[x][0];
    }
    
    int _find(int x) {return fat[x]==x?x:fat[x]=_find(fat[x]);}
    
    ll Kruscal() {
        ll sum = 0;
        sort(tree+1, tree+m+1);
        for (int i = 1; i <= n; ++ i) fat[i] = i;
        for (int i = 1; i <= m; ++ i) {
            int u = tree[i].from, v = tree[i].to;
            ll w = tree[i].w;
            int fx = _find(u), fy = _find(v);
            if (fx != fy) {
                used[i] = 1, sum += tree[i].w, fat[fx] = fy;
                Add_Edge(u, v, w), Add_Edge(v, u, w);
                cntt++;
            }
        }
        return sum;
    }
    
    void init() {
        memset(head, -1, sizeof(head));
        memset(used, 0, sizeof(used));
        memset(depth, 0, sizeof(depth));
        cnt = cntt = 0;
        memset(dis1, 0, sizeof(dis1));
        memset(fa, 0, sizeof(fa));
    
    }
    
    //(非严格)计算u到v路径上的最大值
    ll cal2(int u, int v) {
        ll val=0;
        for (int i = lg[depth[u]]; i >= 0; -- i) {
            //倍增找最大
            if (depth[fa[u][i]] >= depth[v]) {
                val  = max(val, dis1[u][i]);
                u = fa[u][i];
            }
        }
        return val;
    }
    
    ll solve(ll base) {
        ll ans = linf;
        bfs();
        for (int i = 1; i <= m; ++ i) {
            if (used[i]) continue;
            int u = tree[i].from, v = tree[i].to;
            int lca = LCA(u, v);
            ll w = tree[i].w;
            ans = min(ans, base+w-max(cal2(u,lca),cal2(v,lca)));
        }
        return ans;
    }
    
    int main() {
    //    freopen("in.txt","r",stdin); //输入重定向,输入数据将从in.txt文件中读取
    //    freopen("out1.txt","w",stdout); //输出重定向,输出数据将保存在out.txt文件中
        int T;
        scanf("%d", &T);
        for(int i = 1; i < maxn; ++i) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
            lg[i] = lg[i-1] + (1 << lg[i-1] == i);  //看不懂的可以手推一下
        for (int Ti = 1; Ti <= T; ++ Ti) {
            scanf("%d%d", &n, &m);
            init();
            for(int i = 1; i <= m; ++i)
                scanf("%d%d%lld", &tree[i].from, &tree[i].to, &tree[i].w);
            ll base = Kruscal();
            cout << "Case #" << Ti << " : ";
            if (cntt != n-1) {
                cout << "No way" << endl;
                continue;
            }
            if (m == n-1) {
                cout << "No second way" << endl;
                continue;
            }
            cout << solve(base) << endl;
        }
    
        return 0;
    }
    View Code

    最小树形图

    算法流程

    对于每一次循环:

    1. 贪心找出所谓的“最小带环树形图”,(就是上面的万能图的红边)
      顺便记录一下自己是从哪里来的(入边的起点)
    2. 把所有选出的边加到ans里面
    3. 找环记录环,统计数量
    4. 如果没环,代表完成了,退出循环
    5. 把所有不是环上的点全部设置为自己是一个独立环(大小为1的新环)
    6. 重新设置边权&缩点
    7. 完成缩点,重新设置n和root,然后初始

    附一个朱刘算法 O(VE)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    using namespace std;
    typedef long long ll;
    const int maxn = 1000+50;
    const int maxm = 50000+50;
    const int inf = 0x3f3f3f3f;
    inline int read() {
        int a=0;char c=getchar();
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
        return a;
    }
    struct edge{int u,v;ll w;} e[maxm];
    int cnt,fa[maxn],scc[maxn],top[maxn];
    ll minw[maxn];
    //cnt当前图环的数量
    //scc[u]代表u节点在第scc[u]个环中
    //top[u]代表u所在链的代表元素 类似并查集
    //minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u
    
    inline ll zhuliu(int r, int n, int m) {
        ll ans = 0;
        while(1) {
            for (int i = 1; i <= n; ++i) minw[i] = inf;
            for (int i = 1; i <= m; ++i)
                if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) {//不是自环 并且边权比选定的还小
                    fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
                }
            int cnt = 0, u;
            minw[r] = 0;
            for (int i = 1; i <= n; ++ i) {
                scc[i] = top[i] = 0;
                if (minw[i] == inf) return -1;
            }
    
            for (int i = 1; i <= n; ++i) {
                ans += minw[i];
                for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
                if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                    scc[u] = ++cnt;
                    for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
                }
            }
            if (!cnt) return ans; //没环就是找到答案了
            for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环
    
            for (int i = 1; i <= m; ++i) {
                ll last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
                if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
            }
            n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
        }
    }
    
    int main() {
        int T;
        scanf("%d", &T);
        for (int Ti = 1; Ti <= T; Ti++) {
            int n, m;
            scanf("%d%d", &n, &m);
            for(int i = 1,u,v,w; i <= m; i++) {
                scanf("%d%d%lld", &u, &v, &w);
                e[i] = {u+1,v+1,w};
            }
            printf("Case #%d: ", Ti);
            ll ans = zhuliu(1,n,m);
            if (ans != -1) printf("%lld\n", ans);
            else printf("Possums!\n");
        }
        return 0;
    }
    View Code

    效率更好的便是用tajan优化的。O(m+nlogn)

    tajan优化主要在于找最小入边。使用左偏树和按秩合并并查集优化处理 (虽然我不是很懂)。

    存个酷酷的tajan板子 不过这个常数比较大的样子。如果复杂度够的话还是用朱刘吧。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    #define il inline
    #define ri register
    #define Size 100050
    using namespace std;
    int fa[Size],cnt,is[Size];
    il int find(int);
    il void read(int&),Union(int,int);
    struct leftist{
        struct point{
            int l,r,d,v,t,to;
        }a[Size]={{0,0,-1,0,0,0}};
        int r[Size];
        il void merge(int&x,int&y){
            if(!x||!y){x^=y;return;}
            if(a[x].v>a[y].v)swap(x,y);
            a[y].t-=a[x].t,a[y].v-=a[x].t;
            merge(a[x].r,y);
            if(a[a[x].l].d<a[a[x].r].d)
                a[x].l^=a[x].r^=a[x].l^=a[x].r;
            a[x].d=a[a[x].r].d+1;
        }
        il void spread(int&p){
            a[a[p].l].t+=a[p].t,a[a[p].r].t+=a[p].t;
            a[a[p].l].v+=a[p].t,a[a[p].r].v+=a[p].t;
            a[p].t=0;
        }
        il void pop(int&x){
            spread(x),merge(a[x].l,a[x].r),x=a[x].l;
        }
        il point*top(int&x){
            while(r[x]&&!(find(a[r[x]].to)^x))pop(r[x]);
            if(!r[x])puts("-1"),exit(0);
            a[r[x]].to=find(a[r[x]].to);
            return &a[r[x]];
        }
    }L;
    int pre[Size];
    int main(){
        int n,m,r,ans(0);leftist::point*temp;
        read(n),read(m),read(r),cnt=n,is[r]=r;
        for(int i(1),u,v,w;i<=m;++i)
            read(u),read(v),read(w),
                L.a[i]={0,0,0,w,0,u},
                L.merge(L.r[v],u=i);
        for(int i(1);i<=n<<1;++i)fa[i]=i;
        for(int i(1),j(i);i<=n;j=++i)
            while(!is[j]){
                while(!is[j])
                    is[j]=i,j=(temp=L.top(j))->to,
                        ans+=temp->v;if(is[j]^i)break;
                while(~is[j])
                    is[j]=-1,j=pre[j]=(temp=L.top(j))->to,
                        temp->t-=temp->v,temp->v=0;++cnt;
                while(is[j]^i)is[j]=i,Union(j,cnt),j=pre[j];
                j=cnt;
            }return printf("%d",ans),0;
    }
    il void Union(int u,int v){
        if((u=find(u))^(v=find(v)))
            L.merge(L.r[v],L.r[u]),fa[u]=v;
    }
    il int find(int x){
        return x^fa[x]?fa[x]=find(fa[x]):x;
    }
    il void read(int&x){
        x^=x;ri char c;while(c=getchar(),c<'0'||c>'9');
        while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    }
    View Code

    给道裸题吧:E - Command Network POJ - 3164

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    using namespace std;
    typedef long long ll;
    const int maxn = 200+50;
    const int maxm = 50000+50;
    const int inf = 1e10;
    inline int read() {
        int a=0;char c=getchar();
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
        return a;
    }
    struct edge{int u,v;double w;} e[maxm];
    int cnt,fa[maxn],scc[maxn],top[maxn];
    double minw[maxn];
    //cnt当前图环的数量
    //scc[u]代表u节点在第scc[u]个环中
    //top[u]代表u所在链的代表元素 类似并查集
    //minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u
    
    double zhuliu(int r, int n, int m) {
        double ans = 0;
        while(1) {
            for (int i = 1; i <= n; ++i) minw[i] = inf;
            for (int i = 1; i <= m; ++i)
                if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) //不是自环 并且边权比选定的还小
                    fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
    
            int u, cnt = 0;
            minw[r] = 0;
            for (int i = 1; i <= n; ++ i) {
                scc[i] = top[i] = 0;
                if (minw[i] == inf) return -1;
            }
    
            for (int i = 1; i <= n; ++i) {
                ans += minw[i];
                for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
                if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                    scc[u] = ++cnt;
                    for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
                }
            }
    
            if (!cnt) return ans; //没环就是找到答案了
            for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环
    
            for (int i = 1; i <= m; ++i) {
                double last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
                if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
            }
            n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
        }
    }
    double dist(double x1, double y1, double x2, double y2){
        return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
    }
    int main() {
        int n, m;
        while (scanf("%d%d", &n, &m) != EOF) {
            double x[n+1], y[n+1];
            for (int i = 1; i <= n; i++)
                scanf("%lf%lf", &x[i], &y[i]);
            for(int i = 1,u,v; i <= m; i++) {
                scanf("%d%d", &u, &v);
                e[i] = {u,v,dist(x[u],y[u],x[v],y[v])};
            }
            double ans = zhuliu(1,n,m);
            if (ans != -1) printf("%.2f\n",ans);
            else printf("poor snoopy\n");
        }
        return 0;
    }
    View Code

     还有道:F - Teen Girl Squad  UVA - 11183

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    using namespace std;
    typedef long long ll;
    const int maxn = 1000+50;
    const int maxm = 50000+50;
    const int inf = 0x3f3f3f3f;
    inline int read() {
        int a=0;char c=getchar();
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
        return a;
    }
    struct edge{int u,v;ll w;} e[maxm];
    int cnt,fa[maxn],scc[maxn],top[maxn];
    ll minw[maxn];
    //cnt当前图环的数量
    //scc[u]代表u节点在第scc[u]个环中
    //top[u]代表u所在链的代表元素 类似并查集
    //minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u
    
    inline ll zhuliu(int r, int n, int m) {
        ll ans = 0;
        while(1) {
            for (int i = 1; i <= n; ++i) minw[i] = inf;
            for (int i = 1; i <= m; ++i)
                if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) {//不是自环 并且边权比选定的还小
                    fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
                }
            int cnt = 0, u;
            minw[r] = 0;
            for (int i = 1; i <= n; ++ i) {
                scc[i] = top[i] = 0;
                if (minw[i] == inf) return -1;
            }
    
            for (int i = 1; i <= n; ++i) {
                ans += minw[i];
                for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
                if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                    scc[u] = ++cnt;
                    for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
                }
            }
            if (!cnt) return ans; //没环就是找到答案了
            for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环
    
            for (int i = 1; i <= m; ++i) {
                ll last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
                if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
            }
            n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
        }
    }
    
    int main() {
        int T;
        scanf("%d", &T);
        for (int Ti = 1; Ti <= T; Ti++) {
            int n, m;
            scanf("%d%d", &n, &m);
            for(int i = 1,u,v,w; i <= m; i++) {
                scanf("%d%d%lld", &u, &v, &w);
                e[i] = {u+1,v+1,w};
            }
            printf("Case #%d: ", Ti);
            ll ans = zhuliu(1,n,m);
            if (ans != -1) printf("%lld\n", ans);
            else printf("Possums!\n");
        }
        return 0;
    }
    View Code

    之后給道无根最小树形图的题目:G - Ice_cream’s world II HDU - 2121

    感谢这道题目让我发现了板子有点点问题,现在已经更正了(板子upupup)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    using namespace std;
    typedef long long ll;
    const int maxn = 2000+50;
    const int maxm = 50000+50;
    const ll inf = 1e17;
    int rr; //记录实根位置
    ll ans;
    inline int read() {
        int a=0;char c=getchar();
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
        return a;
    }
    struct edge{int u,v;ll w;} e[maxm];
    int cnt,fa[maxn],scc[maxn],top[maxn];
    ll minw[maxn];
    //cnt当前图环的数量
    //scc[u]代表u节点在第scc[u]个环中
    //top[u]代表u所在链的代表元素 类似并查集
    //minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u
    
    inline ll zhuliu(int r, int n, int m) {
        ll ans = 0;
        while(1) {
            for (int i = 1; i <= n; ++i) minw[i] = inf;
            for (int i = 1; i <= m; ++i)
                if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) {//不是自环 并且边权比选定的还小
                    fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
                    //因为有缩点,每个点的下标发生了变化,但新加的边的指向未变,
                    //所以可以通过这个边找到原来点的下标
                    if (e[i].u == r) rr = i;
                }
            int cnt = 0, u;
            minw[r] = 0;
            for (int i = 1; i <= n; ++ i) {
                scc[i] = top[i] = 0;
                if (minw[i] == inf) return -1;
            }
    
            for (int i = 1; i <= n; ++i) {
                ans += minw[i];
                for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
                if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                    scc[u] = ++cnt;
                    for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
                }
            }
            if (!cnt) return ans; //没环就是找到答案了
            for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环
    
            for (int i = 1; i <= m; ++i) {
                ll last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
                if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
            }
            n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
        }
    }
    
    int main() {
        int n, m;
        while (~scanf("%d%d", &n, &m)) {
            ll sum = 0;
            for(int i = 1,u,v; i <= m; i++) {
                ll w;
                scanf("%d%d%lld", &u, &v, &w);
                e[i] = {u+1,v+1,w};
                sum += w;
            }
            sum += 1; //设虚根为n+1
            for (int i = 1; i <= n; ++ i)
                e[i+m] = {n+1,i,sum};
            //cout << sum << endl;
            ll ans = zhuliu(n+1,n+1, n+m);
            //cout << ans << endl;
            if (ans == -1 || ans >= (2*sum)) {
                    //cout << ans << " " << 2*sum << endl;
                    printf("impossible\n\n"); //不存在
            }
            else {
                printf("%lld %d\n\n", ans-sum, rr-m-1) ;
            }
        }
    
        return 0;
    }
    View Code

     还有一道类似于无根MDST的:H - Transfer water HDU - 4009

    这个建点非常酷。

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    using namespace std;
    typedef long long ll;
    const int maxn = 1000+50;
    const int maxm = 1000000+50;
    const int inf = 0x3f3f3f3f;
    inline int read() {
        int a=0;char c=getchar();
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar();
        return a;
    }
    struct edge{int u,v;ll w;} e[maxm];
    int cnt,fa[maxn],scc[maxn],top[maxn];
    ll minw[maxn];
    //cnt当前图环的数量
    //scc[u]代表u节点在第scc[u]个环中
    //top[u]代表u所在链的代表元素 类似并查集
    //minw[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u
    
    inline ll zhuliu(int r, int n, int m) {
        ll ans = 0;
        while(1) {
            for (int i = 1; i <= n; ++i) minw[i] = inf;
            for (int i = 1; i <= m; ++i)
                if (e[i].u != e[i].v && e[i].w < minw[e[i].v]) {//不是自环 并且边权比选定的还小
                    fa[e[i].v] = e[i].u, minw[e[i].v] = e[i].w;
                }
            int cnt = 0, u;
            minw[r] = 0;
            for (int i = 1; i <= n; ++ i) {
                scc[i] = top[i] = 0;
                if (minw[i] == inf) return -1;
            }
    
            for (int i = 1; i <= n; ++i) {
                ans += minw[i];
                for (u = i; u != r && top[u] != i && !scc[u]; u = fa[u]) top[u]=i; //找环 //找到包含不在环中的点最多的链 打上标记
                if(u != r && !scc[u]) { //这时候还满足条件说明vis[u]==i 即成环,将在环里的点打上环的编号
                    scc[u] = ++cnt;
                    for (int v = fa[u]; v != u; v = fa[v]) scc[v] = cnt;
                }
            }
            if (!cnt) return ans; //没环就是找到答案了
            for (int i = 1; i <= n; ++i) if (!scc[i]) scc[i] = ++cnt;//i节点不存在当前树中 就给他自己成一个环
    
            for (int i = 1; i <= m; ++i) {
                ll last = minw[e[i].v]; //last等于当前连进v点的边的最小权值
                if ((e[i].u=scc[e[i].u]) != (e[i].v=scc[e[i].v])) e[i].w -= last; //当前边的两个端点不在同一个环内
            }
            n = cnt, r = scc[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
        }
    }
    int cal(int x1, int y1, int z1, int x2, int y2, int z2) {
        return abs(x1-x2)+abs(y1-y2)+abs(z1-z2);
    }
    int main() {
        int n,X,Y,Z;
           while(scanf("%d%d%d%d", &n, &X, &Y, &Z) != EOF && (n||X||Y||Z)){
            int x[n+1], y[n+1], z[n+1];
            for (int i = 1; i <= n; ++ i)
                scanf("%d%d%d", x+i, y+i, z+i);
            int m = 0;
            for(int u = 1,num,v; u <= n; ++ u){
                scanf("%d", &num);
                while(num--){
                    scanf("%d", &v);
                    e[++m].w = cal(x[u],y[u],z[u],x[v],y[v],z[v]) * Y;
                    if (z[v] > z[u]) e[m].w += Z;
                    e[m].u = u; e[m].v = v;
                }
            }
            for (int i = 1; i <= n; ++i){
                e[++m].u = n+1; e[m].v = i;
                e[m].w = z[i] * X;
            }
            ll ans = zhuliu(n+1,n+1,m);
            printf("%lld\n", ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    MFC project for a non-Unicode character set is deprecated
    关于Visual Studio 2013 编译 multi-byte character set MFC程序出现 MSB8031 错误的解决办法
    字符串比较自实现
    各种语言里获取当前模块的方法:ABAP,ABSL,C,nodejs
    SAP CRM product attachment的document template功能
    ABAP, UI5和webpack的处理入口
    ABAP, Maven, CF App和Webpack的build
    json格式的字符串序列化和反序列化的一些高级用法
    SAP ABAP Netweaver容器化, 不可能完成的任务吗?
    UI Component in CRM WebUI and Hybris
  • 原文地址:https://www.cnblogs.com/Vikyanite/p/13471994.html
Copyright © 2011-2022 走看看