[省选前题目整理][SPOJ OPTM]Optimal Marks(最小割)

8 篇文章 0 订阅
2 篇文章 0 订阅

题目链接

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=17875

题目大意

给定一个 n 个点m条边的无向图,定义这个图上每条边的边权为该边两个端点点权的亦或和,图中部分点的点权已经给定,要你给剩下的其他点也设定点权,使得边权之和最小。

思路

最小割。
我们把每个点的点权看成32位的二进制数,这些二进制数,每一位是0还是1都不会影响其他位。因此我们可以将整个题改成:每个点权均为0或1,某些点权已经确定了是0还是1,这样做32次就行了。

可以发现,亦或和为1的只有0 xor 1,比较容易想到最小割。对于每个已经确定点权的点,若该点为1,源向该点连边,否则该点向汇连边。对于原图中每条无向边 u>v 而言, u v连容量为1的边, v u连容量为1的边。那么做完最小割后,属于 S 侧的点都是点权为1,属于T侧的点都是点权为0,而最小割就是最小边权和。为什么呢?因为若点 uv 点权相同的话,它们之间的边是不会被割掉的,若 u v在原图中有边相连,但是它们的点权互不相同的话, u v在网络流中的边就会被割掉,使得 uv 分别在 ST

代码

写代码时犯了很多SB的错误。。。我太弱了。。。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define INF 0x3f3f3f3f
#define MAXE 10000000
#define MAXV 25000

using namespace std;

int n,m,S,T;

struct edge
{
    int u,v,cap,next;
}edges[MAXE];

int head[MAXV],nCount=0;

void AddEdge(int U,int V,int C)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].cap=C;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

void add(int U,int V,int C)
{
    AddEdge(U,V,C);
    AddEdge(V,U,0);
}

int q[MAXE],layer[MAXV];

bool CountLayer()
{
    memset(layer,-1,sizeof(layer));
    int h=0,t=1;
    q[h]=S;
    layer[S]=1;
    while(h<t)
    {
        int u=q[h++];
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(layer[v]==-1&&edges[p].cap)
            {
                layer[v]=layer[u]+1;
                q[t++]=v;
            }
        }
    }
    return layer[T]!=-1;
}

int DFS(int u,int flow)
{
    if(u==T) return flow;
    int used=0;
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if(layer[v]==layer[u]+1&&edges[p].cap) //!!!!!!
        {
            int tmp=DFS(v,min(flow-used,edges[p].cap));
            used+=tmp;
            edges[p].cap-=tmp;
            edges[p^1].cap+=tmp;
            if(used==flow) return used;
        }
    }
    if(used==0) layer[u]=-1;
    return used;
}

int Dinic()
{
    int maxflow=0;
    while(CountLayer())
        maxflow+=DFS(S,INF);
    return maxflow;
}

bool vis[MAXV],map[6000][6000];
int k,mark[6000],id[6000]; //id[i]=第i个已经确定标号的点

void init()
{
    nCount=1;
    memset(head,-1,sizeof(head));
    memset(vis,false,sizeof(vis));
}

void DFS2(int u,int digit) //在第digit位下做最小割,检查u是否在S侧
{
    vis[u]=true;
    mark[u]|=(1<<digit);
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if(edges[p].cap&&!vis[v])
            DFS2(v,digit); //!!!!!!
    }
}

void buildgraph(int digit)
{
    for(int i=1;i<=k;i++)
    {
        if((1<<digit)&mark[id[i]]) add(S,id[i],INF);
        else add(id[i],T,INF);
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(map[i][j])
                add(i,j,1);
}

int main()
{
    int TestCase;
    scanf("%d",&TestCase);
    while(TestCase--)
    {
        scanf("%d%d",&n,&m);
        S=MAXV-2,T=MAXV-1;
        memset(mark,0,sizeof(mark));
        memset(map,false,sizeof(map));
        for(int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            map[u][v]=map[v][u]=true;
        }
        scanf("%d",&k);
        for(int i=1;i<=k;i++)
        {
            int x;
            scanf("%d",&x);
            scanf("%d",&mark[x]);
            id[i]=x;
        }
        for(int i=0;i<=31;i++)
        {
            init();
            buildgraph(i);
            Dinic();
            DFS2(S,i);
        }
        for(int i=1;i<=n;i++)
            printf("%d\n",mark[i]);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值