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

来源:互联网 发布:java防止sql注入的方法 编辑:程序博客网 时间:2024/04/29 20:51

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>,建<u,v,1><v,u,1>。这样求一遍最小割,把原图划分为s集和e集,其中s集合中所有点为0,e集合中所有点为1。而这次对边权的贡献就是最小割的值。

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

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

还有一种方法比较神…就是把刚刚说的建模的容量乘10000,也就是原边<u,v>,建<u,v,10000><v,u,10000>,然后源点向每个点建<s,u,1>,这样做最小割的时候,肯定要先保证容量大的边少选,然后还要保证容量小的边少选。而同样的割若更靠近源点,容量小的边会选的更多,所以割会靠近汇点。
这样求一遍最大流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
原创粉丝点击