BZOJ 2521: [Shoi2010]最小生成树(最小割)

题意

对于某一条无向图中的指定边 \((a, b)\) , 求出至少需要多少次操作。可以保证 \((a, b)\) 边在这个无向图的最小生成树中.

一次操作指: 先选择一条图中的边 \((u, v)\), 再把图中除了这条边以外的边, 每一条的权值都减少 \(1\) .

\(n \le 500, m \le 800, 1 \le w_i < 10^6\)

题解

给除了一条边的所有边权 \(-1\) ,相当于给这条边的边权 \(+1\)

利用生成树的结论,一条边 \(u \to v\) (权值 \(w\) )若要必定存在于生成树中,那么 \(u \to v\) 所有路径上存在一条边的边权都不能大于 \(w\)

那么我们令这些边的权值为 \(w_i' = \max \{w(a,b)-w_i+1, 0\}\)

然后从 \(a \to b\) 跑一遍最小割就行了。

这样为什么是对的呢?因为只要割掉了所有可行的边,那么就把 \(a, b\) 分成两个连通子图,要连上这两个子图的最小代价就是这个最小割的答案。

总结

对于一些奇怪数据范围的题,可以向网络流上想。

代码

注意连边的时候,需要把正向边和反向边的流量都置为正,这是因为整个图是无向的。

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << x << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}

inline int read() {
    int x = 0, fh = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    return x * fh;
}

void File() {
#ifdef zjp_shadow
    freopen ("2521.in", "r", stdin);
    freopen ("2521.out", "w", stdout);
#endif
}

const int N = 510, M = 810 * 2, inf = 0x3f3f3f3f;

int n, m, id;

namespace Dinic {

    int Head[N], Next[M], to[M], cap[M], e = 1;

    inline void add_edge(int u, int v, int flow) { to[++ e] = v; Next[e] = Head[u]; Head[u] = e; cap[e] = flow; }

    inline void Add(int u, int v, int flow) { add_edge(u, v, flow); add_edge(v, u, flow); }

    int S, T, dis[N];
    bool Bfs() {
        queue<int> Q; Set(dis, 0); Q.push(S); dis[S] = 1;
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            for (int i = Head[u]; i; i = Next[i]) if (cap[i]) {
                int v = to[i]; if (!dis[v]) { dis[v] = dis[u] + 1; if (v == T) return true; Q.push(v); }
            }
        }
        return false;
    }

    int cur[N];
    int Dfs(int u, int flow) {
        if (u == T || !flow) return flow;
        int res = 0, f;
        for (int &i = cur[u]; i; i = Next[i]) if (cap[i]) {
            int v = to[i]; if (dis[v] != dis[u] + 1) continue ;
            if ((f = Dfs(v, min(flow, cap[i])))) {
                cap[i] -= f; cap[i ^ 1] += f;
                res += f; if (!(flow -= f)) break;
            }
        }
        if (!flow || !res) dis[u] = 0;
        return res;
    }

    int Run() {
        int sum_flow = 0;
        while (Bfs()) { Cpy(cur, Head); sum_flow += Dfs(S, inf); }
        return sum_flow;
    }

}

using namespace Dinic;

struct Edge { int u, v, w; } lt[M];

int main () {

    File();

    n = read(); m = read(); id = read();
    For (i, 1, m) {
        int u = read(), v = read(), w = read();
        lt[i] = (Edge){u, v, w};
    }
    
    S = lt[id].u; T = lt[id].v;
    For (i, 1, m) if (id != i) {
        int res = max(lt[id].w - lt[i].w + 1, 0);
        if (res) Add(lt[i].u, lt[i].v, res);
    }

    printf ("%d\n", Run());

    return 0;
}

转载于:https://www.cnblogs.com/zjp-shadow/p/9556688.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值