BZOJ2400:[Spoj 839]Optimal Marks(最小割)

传送门

题意:
给一张无向图,有些点的点值已经给出,有些点的点值可以自由选择。定义一条边的权值为两点异或值。先要求给这些点赋值,使得最后边的权值和最小,并输出此时的点值和,若有多个方案使得边的权值和最小,那么输出使得点值和最小的方案。

题解:
好题啊。。

首先异或每一位是独立的,可以分开来计算。

发现对于每一位考虑,相当于是给没有确定的点赋0,1值,使得相邻点的两两异或最小。

而原问题其实等价于将点分成S,T集合,不同集合的点之间的连边贡献为1,然后就变成了最小割的模板。

然后就是怎么处理点值和最小了。同样对于残量网络缩点,发现S集合此时所在的强联通是最小点集,直接对原点进行dfs就好了。

#include<bits/stdc++.h>
using namespace std;
inline int rd(){
    char ch=getchar();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}
const int Maxn=5e2+50,Maxm=6e3+50;
int n,m,lev[Maxn],cur[Maxm],vis[Maxn],vt,a[Maxn],a2[Maxn],pos; 
queue<int>q;
long long ans;
const int src=0,des=501,INF=0x3f3f3f3f;
struct G{
    int g[Maxn],v[Maxm],c[Maxm],nt[Maxm],ec;
    G(){ec=1;}
    inline void add(int x,int y,int o){++ec;nt[ec]=g[x];g[x]=ec;v[ec]=y;c[ec]=o;}
    inline void init(){
        for(int i=1;i<=n;i++)if(a[i]>=0&&(a[i]&(1<<pos)))add(src,i,INF),add(i,src,0);
        for(int i=1;i<=n;i++)if(a[i]>=0&&(!(a[i]&(1<<pos))))add(i,des,INF),add(des,i,0);
    }
    inline bool bfs(){
        while(!q.empty())q.pop();
        for(int i=0;i<=n;i++)lev[i]=0;
        lev[des]=0;lev[src]=1;q.push(src);
        while(!q.empty()){
            int u=q.front();q.pop();
            for(int j=g[u];j;j=nt[j]){
                if(!c[j]||lev[v[j]])continue;
                lev[v[j]]=lev[u]+1;q.push(v[j]);
                if(v[j]==des)return true;
            }
        }
        return false;
    }
    inline int dinic(const int &x,int f){
        if(x==des)return f;
        int rs=0;
        for(int &j=cur[x];j;j=nt[j]){
            if(!c[j]||(lev[v[j]]!=lev[x]+1))continue;
            int o=dinic(v[j],min(f-rs,c[j]));
            rs+=o;c[j]-=o;c[j^1]+=o;
            if(rs==f)return rs;
        }
        return (lev[x]=0,rs);
    }
    inline void dfs(int now){
        vis[now]=vt;a2[now]|=(1<<pos);
        for(int j=g[now];j;j=nt[j]){
            if(!c[j])continue;
            if(vis[v[j]]!=vt)dfs(v[j]);
        }
    }
    inline int maxflow(){
        int rs=0;
        while(bfs()){
            int t;memcpy(cur,g,sizeof(cur));
            while((t=dinic(src,INF)))rs+=t,memcpy(cur,g,sizeof(cur));
        }
        ++vt;dfs(src);return rs;
    }
}g,g2;
int main(){
    n=rd(),m=rd();
    for(int i=1;i<=n;i++)a[i]=rd();
    for(int i=1;i<=m;i++){
        int x=rd(),y=rd();
        g.add(x,y,1);g.add(y,x,1);
    }
    for(pos=30;pos>=0;pos--){
        g2=g;g2.init();ans+=(1ll<<pos)*g2.maxflow();
    }
    cout<<ans<<endl;ans=0;
    for(int i=1;i<=n;i++)ans+=a2[i];
    cout<<ans<<endl;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值