二分图匹配(必须边, 最大团, 最大独立集/最小割)

题目一 国政议事(二分图最大匹配的必须边)

题目描述

对于任何一个高速发展的发展中国家而言,一个高效的领导小组是不可或缺的。
现在我们知道k国的领导小组有n个人,准备举行一次会议,他们一共需要处理m个重要事项,第i个重要事项在ai手中,并且该重要事项需要交给bi来具体实施。
人都到齐后,他们会进行一个“交换意见”的环节,即每个人都会把自己手中一个自己认为关键的事项i的相关材料转发给该事项的具体实施者bi(如果该人手中没有重要事项,则不进行操作),随后,每个人都从自己收到的重要事项中选择一个自己认为关键的去实施,每实施一个事项,可以获得1点效率。
很显然,领导小组希望在这次会议中的效率更高,请你帮助他们决定在效率最高的情况下,哪些事项是必须执行的。

输入描述

第一行两个正整数n(n<=500),m(m<=20000);
接下来m行,第i+1行两个正整数ai和bi表示重要事项i在ai手中,并且需要交给bi具体实施,可能存在ai=bi的情况

输出描述

第一行一个正整数ans,num表示该会议的最高效率和必须执行的事项个数;

接下来num行,每行有一个正整数,表示在最高效率的情况下,必须执行的事项的标号,按照字典序从小到大输出。

示例1

输入
3 3
1 2
1 3
2 3
输出
2 2
1
3

示例2

输入
4 5
1 4
1 3
2 4
2 3
3 1
输出
3 1
5

思路

思路和这篇博客一样
考虑如下定理:若一条边一定在最大匹配中,则在最终的残量网络中,这条边一定满流,且这条边的两个顶点一定不在同一个强连通分量中。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 10000 + 10;
const int INF = 0x3f3f3f3f;

//注释为弧优化
struct node {
    int form, to, cap, flow, id, next;
} edge[2000006];
int head[maxn];
int cnt;

struct max_Folw {
    int d[maxn], cur[maxn], start, tend;

    bool vis[maxn];


    void init(int s, int t) {
        memset(head, -1, sizeof(head));
        cnt=0;
        start=s, tend=t;
    }

    void add(int start, int to, int cap, int id) {
        edge[cnt].form = start;
        edge[cnt].to = to;
        edge[cnt].cap = cap;
        edge[cnt].flow = 0;
        edge[cnt].id = id;
        edge[cnt].next = head[start];
        head[start] = cnt++;
    }

    void AddEdge(int start, int to, int cap, int id) {
        //cout<<start<<" "<<to<<" "<<cap<<endl;
        add(start, to, cap, id);
        add(to, start, 0, id);
    }

    bool BFS() {
        memset(d, -1, sizeof(d));
        int Q[maxn * 2];
        int Thead, Ttail;
        Thead = Ttail = 0;
        Q[Ttail++] = tend;
        d[tend] = 0;
        while (Thead<Ttail) {
            int x = Q[Thead];
            if (x == start)
                return true;
            for (int i = head[x]; i != -1; i = edge[i].next) {
                int temp = edge[i].to;
                if (d[temp] == -1 && edge[i^1].cap > edge[i^1].flow) { //没有标记,且可行流大于0
                    d[temp] = d[x] + 1;
                    Q[Ttail++] = temp;
                }
            }
            Thead++;
        }
        return false;//汇点是否成功标号,也就是说是否找到增广路
    }

    int DFS(int x, int cap) {
        if (x == tend)
            return cap;
        int flow = 0, f;
        //for (int i = cur[x]; i != -1; i = edge[cur[x]=i].next) {
        for (int i = head[x]; i != -1; i = edge[i].next) {
            int temp = edge[i].to;
            if (d[temp] == d[x] - 1 && edge[i].cap > edge[i].flow) {
                f = DFS(temp, min(cap - flow, edge[i].cap - edge[i].flow));
                edge[i].flow += f;
                edge[i ^ 1].flow -= f;
                flow += f;
                if (flow == cap)
                    return flow;
            }
        }
        d[x] = -2;//防止重搜
        return flow;
    }

    int maxflow() {
        int flow = 0, f;
        while (BFS()) {
            //memcpy(cur, head, sizeof head);
            flow += DFS(start, INF);
        }
        return flow;
    }
} flow;

int scc[maxn];
struct Tarjn{
    int low[maxn];
    int dfn[maxn];
    int vis[maxn];
    int T, N=0;
    stack<int> s;
    void tarjn(int u){
        low[u]=dfn[u]=++T;
        s.push(u), vis[u]=1;
        for(int i=head[u]; i!=-1; i=edge[i].next){
            int to=edge[i].to;
            if(edge[i].cap-edge[i].flow==0) continue;//满流不能再访问
            if(!dfn[to]){//没有访问过
                tarjn(to);
                low[u]=min(low[u], low[to]);
            }
            else if(vis[to]){//在栈中
                low[u]=min(low[u], dfn[to]);
            }
        }
        if(low[u]==dfn[u]){
            ++N;
            while(1){
                int now=s.top(); s.pop();
                vis[now]=0;
                scc[now]=N;
                if(now==u){
                    break;
                }
            }
        }
    }
}ta;

vector<int> G[10005], pos[2], ans;
int vis[10005];
void DFS(int u, int y){
    vis[u]=y; pos[y].push_back(u);
    for(auto x: G[u]){
        if(vis[x]==-1){
            DFS(x, !y);
        }
    }
}
struct Node{
    int x, y;
};

int main() {

    int n, m;
    scanf("%d%d", &n, &m);
    int s=0, t=2*n+1;
    flow.init(s, t);
    for(int i=1; i<=m; i++) {
        int x, y, c;
        scanf("%d%d", &x, &y);
        flow.AddEdge(x, n+y, 1, i);
    }
    for(int i=1; i<=n; i++){
        flow.AddEdge(s, i, 1, m+1);
        flow.AddEdge(n+i, t, 1, m+1);
    }
    int mx=flow.maxflow();
    for(int i=1; i<=n; i++){
        if(!ta.dfn[i]){//强连通缩点
            ta.tarjn(i);
        }
    }

    for(int i=0; i<cnt; i+=2){
        int x=edge[i].form, y=edge[i].to;
        if(x==s||x==t||y==s||y==t) continue;
        if(edge[i].cap-edge[i].flow==0&&scc[x]!=scc[y]){//一定是在最大流中
            ans.push_back(edge[i].id);
        }
    }
    sort(ans.begin(), ans.end());
    printf("%d %d\n", mx, ans.size());
    for(int i=0; i<ans.size(); i++){
        printf("%d\n", ans[i]);
    }

    return 0;
}
/*
5 5 1 5
1 2 5
2 3 3
3 5 3
2 4 2
4 5 2
*/

题目二 [HEOI2012]朋友圈(二分图的最大团)

题目描述

在很久很久以前,曾经有两个国家和睦相处,无忧无虑的生活着。一年一度的评比大会开始了,作为和平的两国,一个朋友圈数量最多的永远都是最值得他人的尊敬,所以现在就是需要你求朋友圈的最大数目。两个国家看成是AB两国,现在是两个国家的描述:
1.A国:每个人都有一个友善值,当两个A国人的友善值a、b,如果a xor b mod 2=1, 那么这两个人都是朋友,否则不是;
2.B国:每个人都有一个友善值,当两个B国人的友善值a、b,如果a xor b mod 2=0 或者 (a or b)化成二进制有奇数个1,那么两个人是朋友,否则不是朋友;
3.A、B两国之间的人也有可能是朋友,数据中将会给出A、B之间“朋友”的情况。
4.在AB两国,朋友圈的定义:一个朋友圈集合S,满足 S∈A∪ B,对于所有的i,j∈ S ,i和j是朋友由于落后的古代,没有电脑这个也就成了每年最大的难题,而你能帮他们求出最大朋友圈的人数吗?

输入描述

第一行t ≤ 6,表示输入数据总数。
接下来t个数据:
第一行输入三个整数A,B,M,表示A国人数、B国人数、AB两国之间是朋友的对数;
第二行A个数ai,表示A国第i个人的友善值;
第三行B个数bi,表示B国第j个人的友善值;
第4——3+M行,每行两个整数(i,j),表示第i个A国人和第j个B国人是朋友。

输出描述

输出t行,每行,输出一个整数,表示最大朋友圈的数目。

示例1

输入
1
2 4 7
1 2
2 6 5 4
1 1
1 2
1 3
2 1
2 2
2 3
2 4
输出
5

说明

【样例说明】
最大朋友圈包含A国第1、2人和B国第1、2、3人。

思路

#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define LL long long
#define inf 999999999
#define lowbit(i) ((i)&(-i))
#define re register
#define maxn 5005
inline int read() {
    int x=0;char c=getchar();while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
std::vector<int> v[maxn];
std::queue<int> q;
struct E{int v,nxt,f;}e[2000005];
int n,m,K,tot,ans,S,T,sz;
int num=1,a[3005],b[3005],vis[3005],pr[3005];
int st[2][5005],top[2];
int head[maxn],cur[maxn],d[maxn];
int U[900005],V[900005];
inline void add(int x,int y,int f) {e[++num].v=y;e[num].nxt=head[x];head[x]=num;e[num].f=f;}
inline void C(int x,int y,int f) {add(x,y,f),add(y,x,0);}
inline int count(int x) {
    int now=0;
    while(x) {now^=1,x-=lowbit(x);}
    return now;
}
inline int BFS() {
    cur[S]=head[S],d[T]=0,cur[T]=head[T];
    for(int i=1;i<=sz;i++) d[pr[i]]=0,cur[pr[i]]=head[pr[i]];
    d[S]=1,q.push(S);
    while(!q.empty()) {
        int k=q.front();q.pop();
        for(int i=head[k];i;i=e[i].nxt)
        if(e[i].f&&!d[e[i].v]) d[e[i].v]=d[k]+1,q.push(e[i].v);
    }
    return d[T];
} 
int dfs(int x,int now) {
    if(x==T||!now) return now;
    int flow=0,ff;
    for(int& i=cur[x];i;i=e[i].nxt) 
    if(d[e[i].v]==d[x]+1) {
        ff=dfs(e[i].v,min(now,e[i].f));
        if(ff<=0) continue;
        now-=ff,flow+=ff,e[i].f-=ff,e[i^1].f+=ff;
        if(!now) break;
    }
    return flow;
}
inline void Dinic(int val) {
    int now=val;
    while(BFS()) {
        now-=dfs(S,inf);
        if(now<=ans) return; 
    }
    ans=now;
}
inline void Connect() {
    num=1;memset(head,0,sizeof(head));
    sz=0;for(int i=1;i<=m;i++) if(vis[i]) pr[++sz]=i;
    for(int i=1;i<=tot;i++) if(vis[U[i]]&&vis[V[i]]) C(U[i],V[i],1);
    for(int i=1;i<=top[0];i++) if(vis[st[0][i]]) C(S,st[0][i],1);
    for(int i=1;i<=top[1];i++) if(vis[st[0][i]]) C(st[1][i],T,1);
}
inline void make(int x) {
    memset(vis,0,sizeof(vis));
    for(int i=0;i<v[x].size();i++) vis[v[x][i]]++;
    int P=1;
    for(int i=1;i<=m;i++) P+=vis[i];
    if(P<=ans) return;
    Connect();Dinic(P);
}
inline void choice(int x,int y) {
    memset(vis,0,sizeof(vis));
    for(int i=0;i<v[x].size();i++) vis[v[x][i]]++;
    for(int i=0;i<v[y].size();i++) vis[v[y][i]]++;
    int P=2;
    for(int i=1;i<=m;i++) {
        if(vis[i]<2) vis[i]=0;else vis[i]=1;
        P+=vis[i];
    }
    if(P<=ans) return;
    Connect();Dinic(P);
}
int main() {
    n=read(),n=read(),m=read(),K=read();S=0,T=m+1;
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=m;i++) {
        b[i]=read();st[b[i]&1][++top[b[i]&1]]=i;
    }
    for(int i=1;i<=m;i++) pr[++sz]=i;
    for(int i=1;i<=top[0];i++)
        for(int j=1;j<=top[1];j++)
            if(!count(b[st[0][i]]|b[st[1][j]])) 
                U[++tot]=st[0][i],V[tot]=st[1][j];
    for(int i=1;i<=tot;i++) C(U[i],V[i],1);
    for(int i=1;i<=top[0];i++) C(S,st[0][i],1);
    for(int i=1;i<=top[1];i++) C(st[1][i],T,1);
    Dinic(m);
    for(int x,y,i=1;i<=K;i++) x=read(),y=read(),v[x].push_back(y);
    for(int i=1;i<=n;i++) make(i);
    for(int i=1;i<=n;i++) 
    	for(int j=i+1;j<=n;j++) if((a[i]^a[j])&1) choice(i,j);
    printf("%d\n",ans);
    return 0;
}

题目三 [TJOI2013]攻击装置(最大独立集)

题目描述

给定一个01矩阵,其中你可以在0的位置放置攻击装置。每一个攻击装置(x,y)都可以按照“日”字攻击其周围的 8个位置(x-1,y-2),(x-2,y-1),(x+1,y-2),(x+2,y-1),(x-1,y+2),(x-2,y+1), (x+1,y+2),(x+2,y+1)
求在装置互不攻击的情况下,最多可以放置多少个装置。

输入描述

第一行一个整数N,表示矩阵大小为N*N。
接下来N行每一行一个长度N的01串,表示矩阵。

输出描述

一个整数,表示在装置互不攻击的情况下最多可以放置多少个装置。

示例1

输入
3
010
000
100
输出
4

思路

和https://www.cnblogs.com/liweihang/p/13995171.html这题不同的是每个格子的权值都为1
我们考虑之前的做法:

对格子黑白染色,就成了一个二分图。好处是每个格子只会和自己颜色不同的格子冲突。我们先假设所有点全部选择。
每个格子向自己冲突的格子连边。那么我们要解决冲突。就是用最小的代价割掉所有边。满足最小割的定理。
因为这里是权值为1,可以直接求二分图的最大独立集。

#include<bits/stdc++.h>
using namespace std;

const int maxn = 2e6 + 10;
const int maxm= 2e6+ 10;
const int INF = 0x3f3f3f3f;

//注释为弧优化
struct max_Folw {
    int d[maxn], cur[maxn], start, tend;
    int Q[maxn * 2];
    struct node {
        int to, cap, flow, next;
    } edge[maxm << 1];

    int head[maxn];
    bool vis[maxn];
    int cnt;

    void init(int s, int t){
        memset(head, -1, sizeof(head));
        cnt=0;
        start=s, tend=t;
    }

    void add(int start, int to, int cap) {
        edge[cnt].to = to;
        edge[cnt].cap = cap;
        edge[cnt].flow = 0;
        edge[cnt].next = head[start];
        head[start] = cnt++;
    }

    void AddEdge(int start, int to, int cap){
        add(start, to, cap);
        add(to, start, 0);
    }

    bool BFS() {
        memset(d, -1, sizeof(d));
        int Thead, Ttail;
        Thead = Ttail = 0;
        Q[Ttail++] = tend;
        d[tend] = 0;
        while (Thead<Ttail) {
            int x = Q[Thead];
            if (x == start)
                return true;
            for (int i = head[x]; i != -1; i = edge[i].next) {
                int temp = edge[i].to;
                if (d[temp] == -1 && edge[i^1].cap > edge[i^1].flow) { //没有标记,且可行流大于0
                    d[temp] = d[x] + 1;
                    Q[Ttail++] = temp;
                }
            }
            Thead++;
        }
        return false;//汇点是否成功标号,也就是说是否找到增广路
    }

    int DFS(int x, int cap) {
        if (x == tend)
            return cap;
        int flow = 0, f;
        //for (int i = cur[x]; i != -1; i = edge[cur[x]=i].next) {
        for (int i = head[x]; i != -1; i = edge[i].next) {
            int temp = edge[i].to;
            if (d[temp] == d[x] - 1 && edge[i].cap > edge[i].flow) {
                f = DFS(temp, min(cap - flow, edge[i].cap - edge[i].flow));
                edge[i].flow += f;
                edge[i ^ 1].flow -= f;
                flow += f;
                if (flow == cap)
                    return flow;
            }
        }
        d[x] = -2;//防止重搜
        return flow;
    }

    int maxflow() {
        int flow = 0, f;
        while (BFS()) {
            //memcpy(cur, head, sizeof head);
            flow += DFS(start, INF);
        }
        return flow;
    }
}flow;
//edge[i].cap-edge[i].flow==0 容量-流量=可流的流量 =0说明已经满流
int a[205][205];
int xx[8]={-1, -2, 1, 2, -1, -2, 1, 2};
int yy[8]={-2, -1, -2, -1, 2, 1, 2, 1};
int main() {
    int n; scanf("%d", &n);
    for(int i=1; i<=n; i++){
        for(int j=1; j<=n; j++){
            scanf("%1d", &a[i][j]);
        }
    }
    flow.init(0, 100001);//s - t
    for(int i=1; i<=n; i++){
        for(int j=1; j<=n; j++){
            for(int k=0; k<8; k++){
                int x=i+xx[k], y=j+yy[k];
                if(x>=1&&x<=n&&y>=1&&y<=n&&a[i][j]==0&&a[x][y]==0){//合法的坐标
                    if((i+j)%2){
                        flow.AddEdge((i-1)*n+j, (x-1)*n+y, INF);
                    }
                    else{
                        flow.AddEdge((x-1)*n+y, (i-1)*n+j, INF);
                    }
                }
            }
        }
    }
    int ans=0;
    for(int i=1; i<=n; i++){
        for(int j=1; j<=n; j++){
            if(a[i][j]) continue;
            ans++;
            if((i+j)%2){
                flow.AddEdge(0, (i-1)*n+j, 1);
            }
            else{
                flow.AddEdge((i-1)*n+j, 100001, 1);
            }
        }
    }
    printf("%d\n", ans-flow.maxflow());

    return 0;
}

posted @   liweihang  阅读(132)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
Live2D
欢迎阅读『二分图匹配(必须边, 最大团, 最大独立集/最小割)』
点击右上角即可分享
微信分享提示