spoj839 Optimal Marks (最小割经典模型 位拆分)

本文介绍了一种使用最小割算法解决特定图问题的方法,即如何为图中未标记的节点分配0或1,使得相邻节点之间的XOR值之和最小。通过构造流量网络并运用最大流算法找到最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://blog.youkuaiyun.com/baidu_23081367/article/details/52403163

题目大意:

给定一张图,每个点有一个数字,有的点没有数字。

给所有点涂上数字,使得任意直接相连的2个点的数字,xor值的和最小。

对于xor运算,每一位之间没有关系。所以题目首先拆分成31个小问题。。求最后每个数字的每一位(二进制位)的数字。

然后目前的问题就是,一张图,很多点数字是0,很多点数字是1,还有一些点没有上数字。 找一个方案,使得任何直接相连的点的数字,xor值的和最小。

显然0附近的点都写0比较好,1附近的点都写1比较好。 最后1和0交汇了,希望交汇的点尽量小。 这显然就是一个最小割。然后新原连1的边,0连新汇,流量都是无穷大。其他原来图中相连的边,都是正反连上流量为1的边即可。跑一个最大流,割的位置,就是01交汇的地方。然后从S出发经过的所有点,都涂上1. 其他点都是0.

代码
#include<stdio.h>
#include<string.h>
 #define INF 0x3fffffff
 #define NN 504
 #define MM 8010

typedef struct node{
    int v, w;
    struct node *nxt, *op;
}NODE;
NODE edg[MM];
NODE *Link[NN]; 
 int h[NN];    
 int num[NN];  // gap优化,标号为i的顶点个数 
int cnt[NN];
int vis[NN];
int mark[NN];
int x[3004], y[3004];

int M, N, idx, S, T, n; // S 表示源点,T表示汇点,n表示节点个数 

void Add(int u, int v, int c1, int c2){
    idx++; //idx记得初始化,不然很容易栈溢出
    edg[idx].v = v;
    edg[idx].w = c1;
    edg[idx].nxt = Link[u];
    edg[idx].op = edg + idx + 1;
    Link[u] = edg + idx;     
    idx++;
    edg[idx].v = u;
    edg[idx].w = c2; // 有向边为0,无向边为c
    edg[idx].nxt = Link[v];
    edg[idx].op = edg + idx - 1;
    Link[v] = edg + idx;
}

int Min(int a, int b){
    return a < b ? a : b;
}

int aug(int u, int flow){
    if (u == T) return flow;
    int l = flow;  // l表示剩余容量 
    int tmp = n - 1;
    for (NODE *p = Link[u]; p; p = p->nxt){
        if (h[u] == h[p->v] + 1 && p->w){
            int f = aug(p->v, Min(l, p->w));
            l -= f;
            p->w -= f;
            p->op->w += f;
            if (l == 0 || h[S] == n) return flow - l; // gap
        }
        if (p->w > 0 && h[p->v] < tmp){
            tmp = h[p->v];  
        }
    }
    if(l == flow){
        num[h[u]]--;   // gap 
        if (num[h[u]] == 0) h[S] = n; // gap,每个点的距离值最多为n - 1,这里设为n 表示断层了 
        else{
            h[u] = tmp + 1;
            num[h[u]]++;   // gap
        }
    }
    return flow - l;
}

void Init(){
    idx = 0;
    S = 0;
    T = N + 1;
    n = T + 1;
    memset(Link, 0, sizeof(Link));
}
/*n表示总点的个数,包括源点和汇点*/
int sap(){
    int ans = 0;
    memset(h, 0, sizeof(h));
    memset(num, 0, sizeof(num));
    num[0] = n;
    while(h[S] < n){
        ans += aug(S, INF); 
    }
    return ans;
}


void dfs(int u, int base){// 所有能搜到的点,都是当前位为1的点 
    cnt[u] += base;
    for (NODE *p = Link[u]; p; p = p->nxt){
        if (p->w && !vis[p->v]){
            vis[p->v] = 1;
            dfs(p->v, base);
        }
    }
}
void Solve(){
    int flag, base, i;
    flag = 1;
    base = 1;
    memset(cnt, 0, sizeof(cnt));// 记录每个点的最后标号 
    while(flag){// 对每一位作处理,一直到最高位为0为止
        flag = 0;
        Init();
        for (i = 1; i <= M; i++){
            Add(x[i], y[i], 1, 1);
        }
        
        for (i = 1; i <= N; i++){
            if (mark[i] != -1){
                if (mark[i] >= 1){
                    flag = 1;
                }
                if (mark[i] % 2){
                    Add(S, i, INF, 0);
                }else{
                    Add(i, T, INF, 0);
                }
                mark[i] /= 2;
            }
        }
        sap();
        memset(vis, 0, sizeof(vis));
        vis[S] = 1;
        dfs(S, base);
        base *= 2;
    }
    for (i = 1; i <= N; i++){
        printf("%d\n", cnt[i]);
    }
}
int main()
{
    int iT, i, K, u, p;
    scanf("%d", &iT);
    while(iT--){
        scanf("%d%d", &N, &M);
        for(i = 1; i <= M; i++){
            scanf("%d%d", &x[i], &y[i]);
        }
        scanf("%d", &K);
        memset(mark, -1, sizeof(mark));
        for (i = 1; i <= K; i++){
            scanf("%d%d", &u, &p);
            mark[u] = p;
        }
        Solve();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值