HDU-6126 Give out candies(最小割)

传送门:HDU-6126

nn个小朋友,标号为11nn,你要给每个小朋友至少11个且至多mm个的糖果。小朋友们共提出kk个要求,每个要求包括三个整数x,y,zx,y,z,表示xx号小朋友得到的糖果数减去yy号小朋友得到的糖果数,结果应当不大于zz。如果你给ii号小朋友jj颗糖果,他会获得w_{i,j}wi,j的满意度,你需要最大化所有小朋友的满意度之和。1\leq n,m\leq50,1\leq k\leq150,1\leq w_{i,j}\leq10^31n,m50,1k150,1wi,j103

题解:网络流最小割

首先考虑没有限制的情况:对于每个人,当然要选取愉悦值最高的情况,如果要用最大流表示,用(i,j)表示第i个人获得j个糖果,愉悦值为w(i,j),边的容量就可以设为F-w(i,j),F为一个足够大的正整数,将(i,j)->(i,j+1)用这条边连接起来,如果这条边是最小割,就表示送给第i个人j个糖果的愉悦值是最高的。再让源点与(i,1)连一条容量为正无穷的边,(i,m)与汇点连容量为F-w(i,m)的边

再考虑有限制的情况:发给第x个人的糖果数-发给第y个人的糖果数不能大于z(这里只考虑z>0的情况)
设第i个人的糖果数为a[i],则a[x]-a[y]<=z --> a[x]-z<=a[y]
因此可以转化为:如果x拿了a[x]个糖果,那么y的糖果就不能少于a[x]-z
要如何表示这种冲突呢?首先肯定要想到建无限容量的边,我们将(x,j)->(y,j-z)连一条容量为正无穷的边,
根据容量无穷大边的性质,在这条边的两端至少有一个割点(不存在x的割在某个大于j的点,y的割在某个小于j-z的点)

当限制条件发生冲突时,比如a[x]<=a[y],a[y]+1<=a[x],这样源点与汇点之间会有一条由正无穷的边构成的路径,因此只要流量为正无穷时,就表示发生了冲突,输出-1

#include<bits/stdc++.h>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const LL mod = 1e9 + 7;
int n, m, k;
const int inf = 0x3f3f3f3f;
const int MX = 2555;
const int MXE = 4 * MX * MX;
struct MaxFlow {
    struct Edge {
        int v, w, nxt;
    } edge[MXE];
    int tot, num, s, t;
    int head[MX];
    void init() {
        memset(head, -1, sizeof(head));
        tot = 0;
    }
    void add(int u, int v, int w) {
        edge[tot].v = v;
        edge[tot].w = w;
        edge[tot].nxt = head[u];
        head[u] = tot++;
        edge[tot].v = u;
        edge[tot].w = 0;
        edge[tot].nxt = head[v];
        head[v] = tot++;
    }

    int  d[MX], vis[MX], gap[MX];
    void bfs() {
        memset(d, 0, sizeof(d));
        memset(gap, 0, sizeof(gap));
        memset(vis, 0, sizeof(vis));
        queue<int>q;
        q.push(t);
        vis[t] = 1;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            for (int i = head[u]; ~i; i = edge[i].nxt) {
                int v = edge[i].v;
                if (!vis[v]) {
                    d[v] = d[u] + 1;
                    gap[d[v]]++;
                    q.push(v);
                    vis[v] = 1;
                }
            }
        }
    }

    int last[MX];
    int dfs(int u, int f) {
        if (u == t) return f;
        int sap = 0;
        for (int i = last[u]; ~i; i = edge[i].nxt) {
            int v = edge[i].v;
            if (edge[i].w > 0 && d[u] == d[v] + 1) {
                last[u] = i;
                int tmp = dfs(v, min(f - sap, edge[i].w));
                edge[i].w -= tmp;
                edge[i ^ 1].w += tmp;
                sap += tmp;
                if (sap == f) return sap;
            }
        }
        if (d[s] >= num) return sap;
        if (!(--gap[d[u]])) d[s] = num;
        ++gap[++d[u]];
        last[u] = head[u];
        return sap;
    }

    int solve(int st, int ed, int n) {
        int flow = 0;
        num = n;
        s = st;
        t = ed;
        bfs();
        memcpy(last, head, sizeof(head));
        while (d[s] < num) flow += dfs(s, inf);
        return flow;
    }
} F;
int code(int i, int j) {
    if (j == m + 1) return n * m + 1;
    return (i - 1) * m + j;
}
int main() {
    int T;
    //freopen("in.txt", "r", stdin);
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d%d", &n, &m, &k);
        F.init();
        for (int i = 1; i <= n; i++) {
            F.add(0, code(i, 1), inf);
            for (int j = 1, w; j <= m; j++) {
                scanf("%d", &w);
                F.add(code(i, j), code(i, j + 1), 1000 - w);
            }
        }
        for (int i = 1, x, y, z; i <= k; i++) {
            scanf("%d%d%d", &x, &y, &z);
            for (int j = 1; j <= m; j++) {
                if (j - z > 0) F.add(code(x, j), code(y, min(j - z, m + 1)), inf);
            }
        }
        int cnt = F.solve(0, n * m + 1, n * m + 2);
        if (cnt >= inf) printf("-1\n");
        else printf("%d\n", n * 1000 - cnt);
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值