P2057 [SHOI2007]善意的投票 (最大流最小割)

P2057 [SHOI2007]善意的投票 / [JLOI2010]冠军调查

P2057 [SHOI2007]善意的投票 (最大流最小割)_第1张图片
最小割,两种意见可以看作源点S和T,我们需要做的是割最少的边使得S和T成为两个不同的集合,解释:割掉的边相当于1次冲突(因为若某边被割走,则显然这条边相连的两个点分别通向了S和T,所以算是一次冲突),当S和T还连通时则必然存在一条路径,这样肯定会有冲突,所以需要使得S和T孤立。

实现时这样建图:直接将S连向同意的人,T连向不同意的人,若两人是朋友,则在他们之间连一条双向边(这里有些人不理解:若两个人有冲突,则只需要其中任意一个人改变意见就行了,简单说是让a同意b的意见或者b同意a的意见,所以只需割掉一条边满足一种情况就可以了,但是有两种情况,所以建双向边)。最后就是求最小割了,直接套上最大流的模板就ok了。

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
/*luogu P3376 【模板】网络最大流*/
typedef long long ll;
typedef pair<int,int> PII;
const ll INF = 1e18;
const int N = 5e2+7;
const int M = 2e5+7;

int head[N],nex[M],ver[M],tot;
ll edge[M];
int n,m,s,t;
ll maxflow;
ll deep[N];//层级数,其实应该是level
int now[M];//当前弧优化
queue<int>q;

inline void read(int &x){
    int f=0;x=0;char c=getchar();
    while(c<'0'||c>'9')f|=c=='-',c=getchar();
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=f?-x:x;
}

inline void add(int x,int y,int z){//建正边和反向边
    ver[tot] = y;
    edge[tot] = z;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

inline bool bfs(){//在残量网络中构造分层图
    for(int i = 0; i <= n + 1; ++ i)deep[i] = INF;
    while(!q.empty())q.pop();
    q.push(s);deep[s] = 0;now[s] = head[s];//一些初始化
    while(!q.empty()){
        int x = q.front();q.pop();
        for(int i = head[x];~i;i = nex[i]){
            int y = ver[i];
            if(edge[i] > 0 && deep[y] == INF){//没走过且剩余容量大于0
                q.push(y);
                now[y] = head[y];//先初始化,暂时都一样
                deep[y] = deep[x] + 1;
                if(y == t)return 1;//找到了
            }
        }
    }
    return 0;
}

//flow是整条增广路对最大流的贡献,rest是当前最小剩余容量,用rest去更新flow
ll dfs(int x, ll flow){//在当前分层图上增广
    if(x == t)return flow;
    ll ans = 0,k,i;
    for(i = now[x];~i && flow;i = nex[i]){
        now[x] = i;//当前弧优化(避免重复遍历从x出发的不可拓展的边)
        int y = ver[i];
        if(edge[i] > 0 && (deep[y] == deep[x] + 1)){//必须是下一层并且剩余容量大于0
            k = dfs(y,min(flow,edge[i]));//取最小
            if(!k)deep[y] = INF;//剪枝,去掉增广完毕的点
            edge[i] -= k;//回溯时更新
            edge[i ^ 1] += k;//成对变换
            ans += k;
            flow -= k;
        }
        //if(!flow)return rest;
    }

    return ans;
}

void dinic(){
    while(bfs())
        maxflow += dfs(s,INF);
}

int main(){
    scanf("%d%d", &n, &m);
    s = 0, t = n + 1;

    memset(head, -1, sizeof head);
    for(int i = 1;i <= n; ++ i){
        int x;
        scanf("%d", &x);
        if(x)add(s, i, 1),add(i, s, 0);
        
        else add(i, t, 1), add(t, i, 0);
    }
    while(m --){
        int x, y;
        scanf("%d%d", &x, &y);
        add(x, y, 1);
        add(y, x, 1);
    }
    dinic();
    printf("%lld\n", maxflow);
    return 0;
}

你可能感兴趣的:(#,最小割)