SPOJ 839 OPTM - Optimal Marks (最小割)(权值扩大,灵活应用除和取模)

http://www.spoj.com/problems/OPTM/

 

题意:

给出一张图,点有点权,边有边权

定义一条边的权值为其连接两点的异或和

定义一张图的权值为所有边的权值之和

已知部分点的点权,自定义其余点的点权

使图的权值最小,并在此基础上使点权和最小

输出点的权值

 

异或——按位做

那么题目就变成了已知一些点的点权为0/1,自定义剩余点的点权0/1

使01相遇的边最少

(01相遇指的是一条边连接的两点的点权不同)

我们建立最小割模型:

先不考虑第二问

源点向已知点的点权为0的点连正无穷的边

已知点的点权为1的点向汇点连正无穷的边

然后把原图加进去,原图中若存在u和v之间的边,就加入u向v,v向u 流量为1的边

这样最小割割的时候只会割流量为1的边,割一条边表示这条边连接的两点点权不同

最后在残量网络上,点与源点相连则代表点权为0,点与汇点相连代表点权为1

再来考虑第二问

第二问相当于最后残量网络上,点既可以与源点连又可以与汇点连的时候,选择与源点连

将所有不与源点相连的点x,加上源点向x流量为1的边

这样如果x最后选择与汇点相连,那么它就要多花费1的代价

这就使x尽可能的与源点相连

那么这岂不是影响了第一问的答案?

用点儿小技巧

原来是原图中若存在u和v之间的边,就加入u向v,v向u 流量为1的边

改成 原图中若存在u和v之间的边,就加入u向v,v向u 流量为10000的边

然后第一问相当于最最小割/10000

第二问相当于最小割%10000

 

最后查询与源点分离的点时,不是源点连出去的流量为1的边满流

而是dinic最后一次分层遍历遍历不到的点

S->2的边虽然满流,但是仍可以通过S->3->2 所以2是与源点相连的点

 

 

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

using namespace std;

#define N 502
#define M 3001

const int inf=1e9;

struct node
{
    int u,v;
}e[3001];

int num[N];

int ans[N];

int front[N],to[M*6],nxt[M*6],cap[M*6],tot;
int src,decc;
int lev[N],cur[N];

queue<int>q;

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); }
}

void add(int u,int v,int w)
{
    to[++tot]=v; nxt[tot]=front[u]; front[u]=tot; cap[tot]=w;
    to[++tot]=u; nxt[tot]=front[v]; front[v]=tot; cap[tot]=0;
}

bool bfs()
{
    for(int i=src;i<=decc;++i) lev[i]=-1,cur[i]=front[i];
    while(!q.empty()) q.pop();
    q.push(src);
    lev[src]=0;
    int now;
    while(!q.empty())
    {
        now=q.front();
        q.pop();
        for(int i=front[now];i;i=nxt[i])
            if(cap[i] && lev[to[i]]==-1)
            {
                lev[to[i]]=lev[now]+1;
                if(to[i]==decc) return true;
                q.push(to[i]);
            }
    }
    return false;
}

int dinic(int now,int flow)
{
    if(now==decc) return flow;
    int rest=0,delta;
    for(int &i=cur[now];i;i=nxt[i])
        if(cap[i] && lev[to[i]]>lev[now])
        {
            delta=dinic(to[i],min(flow-rest,cap[i]));
            if(delta)
            {
                rest+=delta; 
                cap[i]-=delta; cap[i^1]+=delta;
                if(rest==flow) break;
            }
        }
    if(rest!=flow) lev[now]=-1;
    return rest;
}

int main()
{
    int T,n,m,k,x;
//    int flow;
//    long long ans1,ans2;
    read(T);
    while(T--)
    {
        memset(num,-1,sizeof(num));
        memset(ans,0,sizeof(ans));
        //ans1=ans2=0;
        tot=1;
        read(n); read(m);
        for(int i=1;i<=m;++i) read(e[i].u),read(e[i].v);
        read(k);
        for(int i=1;i<=k;++i)
        {
            read(x);
            read(num[x]);
        }
        decc=n+1;
        for(int bit=0;bit<31;++bit)
        {
            memset(front,0,sizeof(front));
            tot=1;
            for(int i=1;i<=n;++i)
                if(num[i]!=-1)
                {
                    if(num[i]&1<<bit) add(i,decc,inf),add(src,i,1);
                    else add(src,i,inf);
                }
                else add(src,i,1);
            for(int i=1;i<=m;++i) 
            {
                add(e[i].u,e[i].v,10000);
                add(e[i].v,e[i].u,10000);
            }
        //    flow=0;
            while(bfs()) 
            dinic(src,inf);
        //    ans1+=1LL*flow/10000*(1<<bit);
        //    ans2+=1LL*flow%10000*(1<<bit);
            for(int i=1;i<=n;++i)
                if(lev[i]==-1) ans[i]|=1<<bit;
        }
    //    printf("%lld\n%lld",ans1,ans2);
        for(int i=1;i<=n;++i) printf("%d\n",ans[i]);
    }
}

 

转载于:https://www.cnblogs.com/TheRoadToTheGold/p/8693787.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值