BZOJ 2400: Spoj 839 Optimal Marks (按位最小割)

28 篇文章 0 订阅

题面

一个无向图,一些点有固定权值,另外的点权值由你来定.
边的值为两点的异或值,一个无向图的值定义为所有边的值之和.

求无向图的最小值

分析

每一位都互不干扰,按位处理.
用最小割算最小值

  • 保留原图的边,容量为1
  • 如果当前点这一位是1,就从S连向当前点,容量为 ∞ \infty
  • 如果当前点这一位是0,就从当前点连向T,容量为 ∞ \infty
    那么这样一来,分在S一边就表示选,分在T一边就表示不选.如果相邻的两点在不同的集合,中间的边就必须断掉,造成1的代价,那么刚好相当于中间的边的值.

跑一遍最小割然后从S开始搜,不搜满流的边(也就是被割开的边),加上答案即可

  • 对于在边值相等的情况下,要求点值最小的正确性如下:
    在我们的最小割中,被划分在S一边表示选,那么一条增广路径上有多条满流边的时候,我们的搜索处理方法是遇到满流边就不往下走.所以我们割的边一定最靠近S集,也就是尽量地多让剩下点被分在T集(也就是选0),这样就满足了在同等情况下尽量选0使得点值最小.
  • 另一种方法是把边值设为10000,点值设为1.那么最小边值之和就是ans/10000,在此情况下的最小点值就是ans%10000

CODE

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long LL;
template<typename T>inline void read(T &num) {
    char ch; int flg=1;
    while((ch=getchar())<'0'||ch>'9')if(ch=='-')flg=-flg;
    for(num=0;ch>='0'&&ch<='9';num=num*10+ch-'0',ch=getchar());
    num*=flg;
}
const int inf = 1e9;
const int MAXN = 505;
const int MAXM = 20005;
int n, m, fir[MAXN], S, T, cnt;
struct edge { int to, nxt; LL c; }e[MAXM];
inline void add(int u, int v, LL cc, LL rc=0) {
    e[cnt] = (edge){ v, fir[u], cc }; fir[u] = cnt++;
    e[cnt] = (edge){ u, fir[v], rc }; fir[v] = cnt++;
}
int dis[MAXN], vis[MAXN], info[MAXN], cur, q[MAXN];
inline bool bfs() {
    int head = 0, tail = 0;
    vis[S] = ++cur; q[tail++] = S;
    while(head < tail) {
        int u = q[head++];
        for(int i = fir[u]; ~i; i = e[i].nxt)
            if(e[i].c && vis[e[i].to] != cur)
                vis[e[i].to] = cur, dis[e[i].to] = dis[u] + 1, q[tail++] = e[i].to;
    }
    if(vis[T] == cur) memcpy(info, fir, (T+1)<<2);
    return vis[T] == cur;
}
LL dfs(int u, LL Max) {
    if(u == T || !Max) return Max;
    LL flow=0, delta;
    for(int &i = info[u]; ~i; i = e[i].nxt)
        if(e[i].c && dis[e[i].to] == dis[u] + 1 && (delta=dfs(e[i].to, min(e[i].c, Max-flow)))) {
            e[i].c -= delta, e[i^1].c += delta, flow += delta;
            if(flow == Max) return flow;
        }
    return flow;
}
inline LL dinic() {
    memset(vis, 0, sizeof vis);
    LL flow=0, x;
    while(bfs()) {
        while((x=dfs(S, inf))) flow+=x;
    }
    return flow;
}
int A[MAXN], X[2005], Y[2005]; LL ans1, ans2;
bool flg[MAXN];
void Getans2(int u, int val) {
    if(u) ans2 += val; flg[u] = 1;
    for(int i = fir[u]; ~i; i = e[i].nxt)
        if(e[i].c && !flg[e[i].to])
            Getans2(e[i].to, val);
}
int main () {
    read(n); read(m); S = 0, T = n+1;
    for(int i = 1; i <= n; ++i) read(A[i]);
    for(int i = 1; i <= m; ++i) read(X[i]), read(Y[i]);
    for(int bit = 0; bit < 30; ++bit) {
        memset(fir, -1, sizeof fir); cnt = 0;
        for(int i = 1; i <= m; ++i) add(X[i], Y[i], 1, 1);
        for(int i = 1; i <= n; ++i) {
            if(A[i] < 0) continue;
            if(A[i]&(1<<bit)) add(S, i, inf);
            else add(i, T, inf);
        }
        memset(flg, 0, sizeof flg);
        ans1 += dinic() * (1<<bit);
        Getans2(S, 1<<bit);
    }
    printf("%lld\n%lld\n", ans1, ans2);
}

U p d : Upd: Upd:不开 l o n g   l o n g long\ long long long毁一生

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值