[HDU 6142] Jedi Council

题目大意: 有一个长度为n的 w 数组, wi只可取 W W 。 有一个长度为p的数组H Hi=ai|wxiwyi|+bi|wyiwzi|+ci|wziwxi|+di(wxiwyi)+ei(wyiwzi)+fi(wziwxi) 。 另有q组限制(x, y, r), 若r==0则 wxwy , 若r==1则 wx==wy , 若r==2则 wx<wy 。 最小化 wi+Hi 。(保证有解, n500,p,q1000,W106,a,b,c,d,e,f1000

思路: 考虑最小割模型。 若设 xi=0 表示i与源点连通, xi=1 表示i与汇点连通。 则最小割= min{(u,v)max(xvxu,0)c} , c为对应边的流量。

观察题目中的式子可以发现, |ab|+(ab)=2max(ab,0) 。 经过一番转换, 要求的表达式可以转换成只含 2max(wiwj,0) wi 的式子(前面还带些系数)。

考虑用最小割来表示这些式子。用 xi=0 表示 wi=W xi=1 表示 wi=W

2amax(wiwj,0)=4aWmax(xixj,0) , 即j向i连一条流量为 4a 的边。(由于每条边的流量上都有个W,所以可以最后再乘)

a>0 , 则 awi=2aWmax(xi0,0)aW , 即s向i连一条流量为 2a 的边, 答案初始值加上 a

a<0 , 则 awi=2(a)Wmax(1xi,0)(a)W , 即i向t连一条流量为 2a 的边, 答案初始值加上 (a)

再考虑那q条限制。
wiwj , 只有 xi=1 xj=0 时才会违反条件, 即 max(xixj,0)inf , j向i连一条流量为inf的边。
wi=wj , 可以转换为 xiwj wjwi
wi<wj , 可以转换为 xi0 1wj

最后的答案即为最小割*W。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>

#define ll long long

using namespace std;

const int N = (int)1e4 + 10;
const int M = (int)1e6 + 10;
const int inf = 1 << 30;

int n, W, p, q, s, t, aoe[N];
int cnt, lst[N], nxt[M], to[M], f[M];

void add(int u, int v, int flow){
    nxt[++ cnt] = lst[u]; lst[u] = cnt; to[cnt] = v; f[cnt] = flow;
    nxt[++ cnt] = lst[v]; lst[v] = cnt; to[cnt] = u; f[cnt] = 0;
}

int head, tail, que[N], d[N];
bool bfs(){
    head = tail = 0;
    que[tail = 1] = s;
    for (int i = 1; i <= t; i ++) d[i] = 0; d[s] = 1;
    while (head < tail){
        int u = que[++ head];
        for (int j = lst[u]; j; j = nxt[j]){
            int v = to[j];
            if (d[v] || !f[j]) continue;
            d[v] = d[u] + 1; que[++ tail] = v;
        }
    }
    return d[t];
}
int dfs(int u, int flow){
    int ret = 0, a;
    if (u == t) return flow;
    for (int j = lst[u]; j; j = nxt[j]){
        int v = to[j];
        if (!f[j] || d[v] != d[u] + 1) continue;
        if (flow && (a = dfs(v, min(f[j], flow)))){
            flow -= a, ret += a;
            f[j] -= a, f[j ^ 1] += a;
        }
    }
    return ret;
}

int main(){
    int T; T = 0; 
    for (scanf("%d ", &T); T --; ){
        scanf("%d %d %d %d", &n, &W, &p, &q);

        cnt = 1;
        s = n + 1, t = s + 1;
        for (int i = 1; i <= t; i ++) lst[i] = 0;
        for (int i = 1; i <= n; i ++) aoe[i] = 0;

        while (p --){
            int x, y, z, a, b, c, d, e, f;
            scanf("%d %d %d %d %d %d %d %d %d", &x, &y, &z, &a, &b, &c, &d, &e, &f);
            add(y, x, 4 * a);
            add(z, y, 4 * b);
            add(x, z, 4 * c);
            aoe[x] += (d - a) - (f - c);
            aoe[y] += (e - b) - (d - a);
            aoe[z] += (f - c) - (e - b);
        }

        for (int i = 1; i <= n; i ++) aoe[i] ++;

        int ans = 0;

        for (int i = 1; i <= n; i ++){
            if (aoe[i] > 0){
                add(s, i, 2 * aoe[i]);
                ans -= aoe[i];
            }
            if (aoe[i] < 0){
                add(i, t, -2 * aoe[i]);
                ans -= -aoe[i];
            }
        }

        while (q --){
            int x, y, r;
            scanf("%d %d %d", &x, &y, &r);
            if (r == 0) add(y, x, inf);
            if (r == 1) add(y, x, inf), add(x, y, inf);
            if (r == 2) add(s, x, inf), add(y, t, inf);
        }

        while (bfs()) ans += dfs(s, inf);

        printf("%lld\n", 1LL * ans * W);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值