有源汇上下界可行流(POJ2396)

题意:给出一个n*m的矩阵的每行和及每列和,还有一些格子的限制,求一组合法方案。

源点向行,汇点向列,连一条上下界均为和的边。

对于某格的限制,从它所在行向所在列连其上下界的边。

求有源汇上下界可行流即可。

具体做法可以从汇点向源点连容量为正无穷的边,转成无源汇上下界可行流。

然后可以新建超级源汇,对于一条下界为l,上界为r的边(x,y),从超级源点向y,x向超级汇点连容量为l的边,x向y连容量为r-l的边。

如果那些容量为l的边没满流,则无解。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

const int inf = 0x3f3f3f3f, N = 230, M = 25000;
char op[2];
int q,n,m,c,x,y,z,s,t,S,T,e=1,fr,l[205][25],r[205][25],ans[205][25],hd[N],nxt[M],to[M],f[M],ch[N];
void add(int x, int y, int z) {
    to[++e] = y, f[e] = z, nxt[e] = hd[x], hd[x] = e;
    to[++e] = x, f[e] = 0, nxt[e] = hd[y], hd[y] = e;
}
void upd(int x, int y) {
    if(op[0] == '<') r[x][y] = min(r[x][y], z-1);
    else if(op[0] == '>') l[x][y] = max(l[x][y], z+1);
    else l[x][y] = max(l[x][y], z), r[x][y] = min(r[x][y], z);
}

bool tel() {
    memset(ch, -1, sizeof ch);
    queue<int> q;
    q.push(S), ch[S] = 0;
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = hd[u]; i; i = nxt[i]) if(ch[to[i]] == -1 && f[i]) ch[to[i]] = ch[u]+1, q.push(to[i]);
    }
    return ch[T] != -1;
}
int zng(int a, int b) {
    if(a == T) return b;
    int r = 0;
    for(int i = hd[a]; i && b > r; i = nxt[i]) if(ch[to[i]] == ch[a]+1 && f[i]) {
        int rr = zng(to[i], min(b-r, f[i]));
        f[i] -= rr, f[i^1] += rr, r += rr;
    }
    if(!r) ch[a] = -1;
    return r;
}

int main() {
    scanf("%d", &q);
    while(q--) {
        e = 1;
        memset(hd, 0, sizeof hd);
        memset(l, 0, sizeof l);
        memset(r, 0x3f, sizeof r);
        scanf("%d%d", &n, &m), t = n+m+1, S = n+m+2, T = n+m+3, add(t,s,inf);
        for(int i = 1; i <= n; i++) scanf("%d", &x), add(S,i,x), add(s,T,x);
        for(int i = 1; i <= m; i++) scanf("%d", &x), add(S,t,x), add(i+n,T,x);
        scanf("%d", &c);
        while(c--) {
            scanf("%d%d%s%d", &x, &y, op, &z);
            if(!x && !y) {
                for(int i = 1; i <= n; i++)
                for(int j = 1; j <= m; j++)
                    upd(i,j);
            } else if(!x) {
                for(int i = 1; i <= n; i++) upd(i,y);
            } else if(!y) {
                for(int i = 1; i <= m; i++) upd(x,i);
            } else upd(x,y);
        }
        if(fr) puts("");
        fr = 1;
        for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++) {
            if(r[i][j] < l[i][j]) {puts("IMPOSSIBLE"); goto aa;}
            add(i,j+n,r[i][j]-l[i][j]),add(S,j+n,l[i][j]),add(i,T,l[i][j]);
        }
        while(tel()) while(zng(S,inf));
        for(int i = hd[S]; i; i = nxt[i]) if(f[i]) {puts("IMPOSSIBLE"); goto aa;}
        for(int i = 1; i <= n; i++)
        for(int j = hd[i]; j; j = nxt[j])
        if(to[j] > n && to[j] <= n+m) ans[i][to[j]-n] = f[j^1];
        for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            printf("%d%c", ans[i][j]+l[i][j], " \n"[j==m]);
        aa: ;
    }
    return 0;
}

转载于:https://www.cnblogs.com/juruolty/p/6204025.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值