二分图匹配(必须边, 最大团, 最大独立集/最小割)
题目一 国政议事(二分图最大匹配的必须边)
题目描述
对于任何一个高速发展的发展中国家而言,一个高效的领导小组是不可或缺的。
现在我们知道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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于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)