【bzoj2400】Spoj 839 Optimal Marks 二进制+最小割

该博客介绍了一种使用最小割方法解决无向图中,如何在保证图的值最小的前提下,使得所有点的值之和最小的问题。通过二进制按位处理,将节点划分为两部分,并构建源点和汇点的特殊连接,通过求解最小割得到最优解。同时,文章讨论了如何在最小割的基础上兼顾点权和最小,提出了扩大第一关键字的值来优化两种权值的选择策略。
摘要由CSDN通过智能技术生成

Description

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

Input

第一行,两个数n,m,表示图的点数和边数。
接下来n行,每行一个数,按编号给出每个点的值(若为负数则表示这个点的值由你决定,值的绝对值大小不超过10^9)。
接下来m行,每行二个数a,b,表示编号为a与b的两点间连一条边。(保证无重边与自环。)

Output

第一行,一个数,表示无向图的值。
第二行,一个数,表示无向图中所有点的值的和。

Sample Input

    3 2

    2

    -1

    0

    1 2

    2 3

Sample Output

    2

    2

HINT

数据约定

n<=500,m<=2000

样例解释

2结点的值定为0即可。

Source


二进制,按位处理。

首先%Oxer的题解

对于每一位单独处理。
每位只有两种可能:0或者1。所以可以把点集划分为两部分。考虑异或:相同为0,不同为1。也就是说只有两集合相邻部分会对答案有贡献,而我们的目标是贡献最小。

简单地说,就是把一个点集划分为两个集合,使它们相交部分贡献最小。这不就是最小割!

建模:源点向每个这位为0的点连INF边,这位为1的点向汇点连INF边。因为是无向图,所以原边 <u,v> <script type="math/tex" id="MathJax-Element-786"> </script>,建 <u,v,1><v,u,1> <script type="math/tex" id="MathJax-Element-787"> </script>。这样求一遍最小割,把原图划分为s集和e集,其中s集合中所有点为0,e集合中所有点为1。而这次对边权的贡献就是最小割的值。

然而在顾及边权和最小时,没有顾及点权和最小。换句话说就是边权和一样的时候不一定保证点权和最小。贪心的想:若想要点权和最小,则s集合中的点一定尽量多,也就是说当最小割有多个的时候,选靠近汇点的那个割。

有一种方法是从汇点dfs,找到的点全部标为1。仔细想想好像没什么错,因为最靠近汇点的割也是割,它会阻碍汇点dfs到更多的点。

还有一种方法比较神…就是把刚刚说的建模的容量乘10000,也就是原边 <u,v> <script type="math/tex" id="MathJax-Element-788"> </script>,建 <u,v,10000><v,u,10000> <script type="math/tex" id="MathJax-Element-789"> </script>,然后源点向每个点建 <s,u,1> <script type="math/tex" id="MathJax-Element-790"> </script>,这样做最小割的时候,肯定要先保证容量大的边少选,然后还要保证容量小的边少选。而同样的割若更靠近源点,容量小的边会选的更多,所以割会靠近汇点。
这样求一遍最大流ans,对边权和贡献是ans/10000,对点权和是ans%10000。

这种思想简单来说就是扩大第一关键字的值,在第一关键字相同使考虑第二关键字,实现两种权值选优的目的。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;

typedef long long LL;

const LL INF = 10000000000000000ll;
const int SZ = 1000010;
const LL mod = 10000;

int head[SZ],nxt[SZ],tot = 1;

struct edge{
    int t;
    LL d;
}l[SZ];

void build(int f,int t,LL d)
{
    l[++ tot].t = t;
    l[tot].d = d;
    nxt[tot] = head[f];
    head[f] = tot;
}

void insert(int f,int t,LL d)
{
    build(f,t,d); build(t,f,0);
}

int deep[SZ];
queue<int> q;

bool bfs(int s,int e)
{
    memset(deep,0,sizeof(deep));
    deep[s] = 1;
    while(q.size()) q.pop();
    q.push(s);
    while(q.size())
    {
        int u = q.front(); q.pop();
        for(int i = head[u];i;i = nxt[i])
        {
            int v = l[i].t;
            if(!deep[v] && l[i].d)
            {
                deep[v] = deep[u] + 1;
                q.push(v);
                if(v == e) return true;
            }
        }
    }
    return false;
}

LL dfs(int u,LL flow,int e)
{
    if(e == u || flow == 0) return flow;
    LL rest = flow;
    for(int i = head[u];i;i = nxt[i])
    {
        int v = l[i].t;
        if(deep[v] == deep[u] + 1 && l[i].d)
        {
            LL f = dfs(v,min(rest,l[i].d),e);
            if(f > 0)
            {
                l[i].d -= f;
                l[i ^ 1].d += f;
                rest -= f;
                if(rest == 0) break;
            }
            else deep[v] = 0;
        }
    }
    return flow - rest;
}

LL dinic(int s,int e)
{
    LL ans = 0;
    while(bfs(s,e)) ans += dfs(s,INF,e);
    return ans;
}



int ff[SZ],tt[SZ],val[SZ],n,m;

void init()
{
    tot = 1;
    memset(head,0,sizeof(head));
}

void build_graph(int x,int s,int e)
{
    init();
    for(int i = 1;i <= n;i ++)
    {
        insert(s,i,1);
        if(val[i] >= 0)
        {
            if(val[i] & (1 << x))
                insert(i,e,INF);
            else
                insert(s,i,INF);
        }
    }
    for(int i = 1;i <= m;i ++)
        insert(ff[i],tt[i],mod),insert(tt[i],ff[i],mod);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i ++)
        scanf("%d",&val[i]);
    for(int i = 1;i <= m;i ++)
        scanf("%d%d",&ff[i],&tt[i]);
    int s = n + 1,e = n + 2;
    LL ans1 = 0,ans2 = 0;
    for(int i = 30;i >= 0;i --)
    {
        build_graph(i,s,e);
        LL sum = dinic(s,e);
        ans1 += sum / mod * (1ll << i);
        ans2 += sum % mod * (1ll << i);
    }
    printf("%lld\n%lld",ans1,ans2);
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值