bzoj2400[Spoj 839] Optimal Marks(网络流)

题目链接

题面描述:
定义无向图中的一条边的值为:这条边连接的两个点的值的异或值。
定义一个无向图的值为:这个无向图所有边的值的和。
给你一个有n个结点m条边的无向图。其中的一些点的值是给定的,而其余的点的值由你决定(但要求均为非负数),使得这个无向图的值最小。在无向图的值最小的前提下,使得无向图中所有点的值的和最小。

分析:
看到异或,当然先想到了线性积,但是由于有不确定元素,就想到了这道题

对付异或,一个有力的方法就是拆位
异或的运算法则:相同为0,不同为1
只有这一位是1的时候才会产生代价

实际上就相当于把这些点分配到两个集合中,点之间有关联关系
如果有关联的点在同一个集合,就不会产生代价,否则会产生 2i 2 i 的贡献

舒老师说这是ISA讲过的集合划分经典题
建图如下:
枚举二进制的每一位 i i
填源S加汇T,这一位是1的点和S连单向边,容量为INF(保证不会割掉这条边)
这一位是0的点向T连单向边,容量为INF
有关联关系的点对之间连双向边,容量为1
求得最小割X,那么代价即为 X2i X ∗ 2 i
(如果割掉了这条边,说明这条边链接的两个点难免要分配到两个集合中从而产生代价)
这里写图片描述

跑完最大流后,在残量网络中dfs寻找S集合,也就是这些点都属于1

tip

注意数据范围

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>

using namespace std;

const int INF=0x33333333;
const int N=30010;
struct node{
    int y,nxt,v;
};
node way[2000010];
int st[N],tot,s,t,n,m,K,x[N],y[N],id[N],ans[N];
int deep[N];

void add(int u,int w,int z) {
    tot++;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
}

int bfs(int s,int t) {
    memset(deep,-1,sizeof(deep));
    deep[s]=1;
    queue<int> q;
    q.push(s);
    while (!q.empty()) {
        int now=q.front(); q.pop();
        for (int i=st[now];i!=-1;i=way[i].nxt)
            if (way[i].v&&deep[way[i].y]==-1) {
                deep[way[i].y]=deep[now]+1;
                q.push(way[i].y);
            }
    }
    return deep[t]!=-1;
}

int dfs(int now,int t,int limit) {
    if (now==t||!limit) return limit;
    int f,flow=0;
    for (int i=st[now];i!=-1;i=way[i].nxt)
        if (way[i].v&&deep[way[i].y]==deep[now]+1&&(f=dfs(way[i].y,t,min(limit,way[i].v)))) {
            flow+=f;
            limit-=f;
            way[i].v-=f;
            way[i^1].v+=f;
            if (!limit) break;
        }
    return flow;
}

void doit(int s,int t) {
    int ans=0;
    while (bfs(s,t))
        ans+=dfs(s,t,INF);
}

void ss(int now,int o) {
    for (int i=st[now];i!=-1;i=way[i].nxt)
        if (way[i].v&&deep[way[i].y]==0) {
            deep[way[i].y]=1;
            ans[way[i].y]|=o;
            ss(way[i].y,o);
        }
}

void solve() {
    bool flag=1;
    s=0,t=n+1;
    for (int _=0;_<=31;_++) {
        memset(st,-1,sizeof(st)); tot=-1;
        for (int i=1;i<=K;i++)
            if ((ans[id[i]]>>_)&1) add(s,id[i],INF),add(id[i],s,0);
            else add(id[i],t,INF),add(t,id[i],0);
        for (int i=1;i<=m;i++) 
            add(x[i],y[i],1),add(y[i],x[i],1);
        doit(s,t);

        memset(deep,0,sizeof(deep));
        deep[s]=1; ss(s,1<<_);
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    while (T--) {
        scanf("%d%d",&n,&m);
        for (int i=1;i<=m;i++) 
            scanf("%d%d",&x[i],&y[i]);
        scanf("%d",&K);
        memset(ans,0,sizeof(ans));
        for (int i=1;i<=K;i++) {
            int x,y;
            scanf("%d%d",&x,&y);
            ans[x]=y;
            id[i]=x;
        }
        solve();
        for (int i=1;i<=n;i++) printf("%d\n",ans[i]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值