网络流 最小割

标签:网络流 最小割

原题 洛谷P2057 善意的投票

题目描述

幼儿园里有n个小朋友打算通过投票来决定睡不睡午觉。对他们来说,这个问题并不是很重要,于是他们决定发扬谦让精神。虽然每个人都有自己的主见,但是为了照顾一下自己朋友的想法,他们也可以投和自己本来意愿相反的票。我们定义一次投票的冲突数为好朋友之间发生冲突的总数加上和所有和自己本来意愿发生冲突的人数。

我们的问题就是,每位小朋友应该怎样投票,才能使冲突数最小?

输入输出格式

输入格式:
文件的第一行只有两个整数n,m,保证有2≤n≤300,1≤m≤n(n-1)/2。其中n代表总人数,m代表好朋友的对数。文件第二行有n个整数,第i个整数代表第i个小朋友的意愿,当它为1时表示同意睡觉,当它为0时表示反对睡觉。接下来文件还有m行,每行有两个整数i,j。表示i,j是一对好朋友,我们保证任何两对i,j不会重复。

输出格式:
只需要输出一个整数,即可能的最小冲突数。

输入输出样例

输入样例#1:
3 3
1 0 0
1 2
1 3
3 2
输出样例#1:
1
说明

2≤n≤300,1≤m≤n(n-1)/2。

要么投睡觉要么不睡,所以应分成两个集合,因为可以跟着好朋友改意见,所以可以连着边的人都可以投同一个票。每个人只有两种选择,所以肯定和s点或t点连着,所以如果割变是与源汇点连着,则算是与自己的意愿冲突,如果割在朋友间则是与朋友的冲突。每割断一对好友或自身意愿。冲突数加一,所以就是求最小割。最小割等于最大流,用dinic即可。(dinic请看模板dinic模板
注意的是一对好友之间可以互相改意见,所以直接建双向边(注意是双向边,和原来算法的反向边区分好,这是本题需要,反向边是为了保证算法正确,都要建,刚开始我就被坑了)。自己再建一个s点表示一种最初观点相同的人,建一个t点表示另一种。我是投1的连s,投0的人连t。
代码在此

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#define N 310
#define M 100000
using namespace std;

int m,n,ans;
queue<int>team;
struct E{
    int to,next,c;
}a[M*2];int last[N],dis[N],cnt;

void build(int u,int v,int c)
{
    a[++cnt].to=v;a[cnt].next=last[u];
    a[cnt].c=c;last[u]=cnt;
    a[++cnt].to=u;a[cnt].next=last[v];
    a[cnt].c=0;last[v]=cnt;
}
bool bfs()
{
    memset(dis,-1,sizeof(dis));
    team.push(305);dis[305]=0;
    while(!team.empty()){
        int x=team.front();team.pop();
        for(int i=last[x];i;i=a[i].next){
            if(a[i].c&&dis[a[i].to]==-1){
                dis[a[i].to]=dis[x]+1;
                team.push(a[i].to);
            }       
        }
    }

    return dis[306]>-1;
}

int dfs(int x,int f)
{
    if(x==306)return f;
    int now=0,b;
    for(int i=last[x];i;i=a[i].next)
    if(a[i].c&&dis[a[i].to]==dis[x]+1){
    b=dfs(a[i].to,min(f-now,a[i].c));
    a[i].c-=b;a[i^1].c+=b;now+=b;
    if(now==f)break;    
    }
    if(!now)dis[x]=-1;
    return now;
}
int main()
{
    freopen("in.txt","r",stdin);
    freopen("1.txt","w",stdout); 
    scanf("%d%d",&n,&m);
     ans=0; cnt=1;
    for(int i=1;i<=n;++i){
        int vot;scanf("%d",&vot);
        if(vot==1)build(305,i,1);
        else      build(i,306,1);
    }

    for(int i=1;i<=m;++i){
    int u,v;    
    scanf("%d%d",&u,&v);
    build(u,v,1);build(v,u,1);//双向边。注意build函数里的是反边 
    }
    while(bfs())
    ans+=dfs(305,0x7fffffff);
    printf("%d\n",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值