二分图
二分图的判定:
二分图: 让很多点分别放在2 端 ,每一个端内的点不能够相连接 , 2端的点可以任意连接。
染色:
小注意: 每一个点没有去过,就去,不要dfs(1) 就走了
color[1]=1; bool dfs(int u) { for(ri i=head[u];i;i=bian[i].net) { int v=bian[i].to; if(color[v]&&color[v]!=color[u]) continue; if(color[v]&&color[v]==color[u]) return 0; if(!color[v]) { color[v]=3-color[u]; if(!dfs(v)) return 0; } } return 1; }
二分图的最大匹配:
bool dfs(int u) { for(int j=son[u];j;j=bian[j].net) { int v=bian[j].val; if(vis[v]) continue ; vis[v]=1; if(pipei[v]==-1||dfs(pipei[v])) { pipei[u]=a; return true; } } return false; } for(i=1;i<=n;i++) { if(pipei[i]==-1) { memset(vis,0,sizeof vis); ans=ans+dfs(i); } }
小定理: 除去最大匹配的点后,剩下的点一定不会相连接。
相关定理: 转载:罗茜 (定理特别重要,题目的应用就是看这个)
最小顶点覆盖:在二分图中寻找一个尽量小的点集,使图中每一条边至少有一个点在该点集中。
最小顶点覆盖 == 最大匹配。
反证法证明:假设当前存在一条两个端点都不在最小顶点覆盖点集中,那么这么光芒四射的边定可以增大最大匹配边集,与最大匹配矛盾,所以得证。
最小路径覆盖:在二分图中寻找一个尽量小的边集,使图中每一个点都是该边集中某条边的端点。
最小路径覆盖 == 顶点数 - 最大匹配。
证明:因为一条边最多可以包含两个顶点,所以我们选边的时候让这样的边尽量多,也就是说最大匹配的边集数目咯。剩下的点就只能一个边连上一个点到集合里啦。
最大独立集:在N个点中选出来一个最大点集,使这个点集中的任意两点之间都没有边。
最大独立集 == 顶点数 - 最大匹配。
证明:因为去掉最大匹配两端的顶点去掉以后,剩下的点肯定是独立集。我们再从每个匹配里面挑选出来一个点加入到独立集中,也是不会破坏原有独立集的独立性的。
应用:棋盘上的骑士,问题(不能相互攻击,问能放多少个骑士)
一些题目的联系:
题目: 下面2道题 建边很关键
问题 D: 【高级算法】火力网 时间限制: 1 Sec 内存限制: 128 MB 提交: 13 解决: 11 [提交] [状态] [讨论版] [命题人:外部导入] 题目描述 给出一个N*N的网格,用'.'表示空地,用'X'表示墙。在网格上放碉堡,可以控制所在的行和列,但不能穿过墙。 问:最多能放多少个碉堡? 例如,下图最多可放5个碉堡(黑色圆点) 输入 第1行:一个整数N(N<=20) 接下来N行,每行N个字符 输出 第1行:1个整数,表示最多可放碉堡数。 样例输入 Copy 4 .X.. .... XX.. .... 样例输出 Copy 5
代码:
#include <bits/stdc++.h> using namespace std; const int M = 10005; const int N = 10005; #define ri register int bool mp[1010][1010]; int n,m,f[1010][1010]; int vis[N],pipei[N]; vector <int> q[10010]; void init() { int cent=1; for(ri j=1;j<=n;j++) { for(ri i=1;i<=n;i++) { if(!mp[i][j]) f[i][j]=cent; else if(!mp[i-1][j]) cent++; } cent++; } m=cent,cent++; for(ri i=1;i<=n;i++) { for(ri j=1;j<=n;j++) { if(!mp[i][j]) q[f[i][j]].push_back(cent); else if(!mp[i][j-1]) cent++; } cent++; } } bool dfs1(int u) { for(ri i=0;i<q[u].size();i++) { int v=q[u][i]; if(vis[v]) continue; vis[v]=1; if(!pipei[v]||dfs1(pipei[v])) { pipei[v]=u; return 1; } } return 0; } void getans() { int ans=0; for(ri i=1;i<=m;i++) { memset(vis,0,sizeof vis); if(!pipei[i]) ans+=dfs1(i); } printf("%d\n",ans); } int main(){ scanf("%d\n",&n); for(ri i=1;i<=n;i++) for(ri j=1;j<=n;j++) { char c; cin>>c; if(c=='X') mp[i][j]=1; } init(); getans(); }
简单例题: 一定仔细要看,特别是注释
P3355 骑士共存问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <bits/stdc++.h> using namespace std; #define ri register int #define M 1005 template <class G> void read(G &x) { x=0;int f=0;char ch=getchar(); while(ch<'0'||ch>'9'){x|=ch=='-';ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x=f?-x:x; return ; } int n,m; vector <int> p[M]; int pi[M],vis[M],flag[M]; bool dfs(int a) { for(ri i=0;i<p[a].size();i++) { int b=p[a][i]; // 看清楚是对哪一边的标记 if(vis[b]) continue; // 访问过的点一定要返回 vis[b]=1; if(!pi[b]||dfs(pi[b])) { pi[b]=a; return 1; } } return 0; } int main(){ read(n);read(m);read(m); for(ri i=1;i<=m;i++) { int a,b; read(a);read(b); p[a].push_back(b); //p[b].push_back(a); // 注意这个不是那种无向图,边不要随便连接 } long long ans=0; for(ri i=1;i<=n;i++) { memset(vis,0,sizeof(vis)); if(dfs(i)) ans++; } printf("%lld",ans); return 0; }
题目:
问题 C: [Balic2001]棋盘上的骑士 时间限制: 2 Sec 内存限制: 64 MB 提交: 8 解决: 5 [提交] [状态] [讨论版] [命题人:外部导入] 题目描述 一个N*N的棋盘上,有一些小方格被拿走了,不能放置骑士,其它位置可以放。现要在棋盘上放若干骑士,要求任一个骑士都不能在其他骑士的攻击点上。请算出棋盘上最多能有几个骑士。骑士攻击范围如图所示(S是骑士的位置,X表示马的攻击点) 输入 第一行包含2个整数n和m,用单个的空格分开,1<=n<=200 , 0<=m < 40000;n 是国际象棋棋盘的大小,m是被拿走的格子数。 下面m行每行包含 2 个整数:x和y,用单个的空格分开,1<=x,y<=n,这些是被拿走的格子的坐标。 棋盘的左上角的坐标是(1,1),右下角是(n,n)。拿走的格子没有重复的。 输出 一个整数,它应该是能放在国际象棋棋盘上的互不攻击对方的马的最大的数量。 样例输入 Copy 3 2 1 1 3 3 样例输出 Copy 5
代码:
#include <bits/stdc++.h> using namespace std; const int N = 10005; const int M = 20002; #define ri register int int m,n; bool mp[1010][1010]; int g[1010][1010],xq[M],yq[M],link[M],vis[M]; int X[20]={2,1,-1,-2,-2,-1,1,2}; int Y[20]={1,2,2,1,-1,-2,-2,-1}; bool check(int x,int y) { if(x<1||y<1||y>n||x>n||mp[x][y]) return 0; return 1; } bool dfs(int u) { int x=xq[u]; int y=yq[u]; int jishu1=7; while(jishu1>=0) { int xx=x+X[jishu1]; int yy=y+Y[jishu1--]; if(!check(xx,yy)) continue; int trmp=g[xx][yy]; if(vis[trmp]) continue; vis[trmp]=1; if(!link[trmp]||dfs(link[trmp])) { link[trmp]=u; return 1; } } return 0; } int main(){ scanf("%d%d",&n,&m); for(ri i=1;i<=m;i++) { int a,b; scanf("%d%d",&a,&b); mp[a][b]=1; } int one=0,two=0; for(ri i=1;i<=n;i++) for(ri j=1;j<=n;j++) { if(mp[i][j]) continue; if(!((i+j)&1)) { g[i][j]=++two; } else { xq[++one]=i; yq[one]=j; } } int ans=n*n-m; for(ri i=1;i<=one;i++) { memset(vis,0,sizeof vis); if(dfs(i)) ans--; } printf("%d\n",ans); }
题目:二分图+floyed(为什么不能加匹配呢)
1995: 【NOIP模拟赛】捉迷藏 时间限制: 1 Sec 内存限制: 128 MB 提交: 9 解决: 3 [提交] [状态] [讨论版] [命题人:外部导入] 题目描述 vani和cl2在一片树林里捉迷藏…… 这片树林里有N座房子,M条有向道路,组成了一张有向无环图。 树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。如果从房子A沿着路走下去能够到达B,那么在A和B里的人是能够相互望见的。 现在cl2要在这N座房子里选择K座作为藏身点,同时vani也专挑cl2作为藏身点的房子进去寻找,为了避免被vani看见,cl2要求这K个藏身点的任意两个之间都没有路径相连。 为了让vani更难找到自己,cl2想知道最多能选出多少个藏身点? 输入 第一行两个整数N,M。 接下来M行每行两个整数x、y,表示一条从x到y的有向道路。 输出 一个整数K,表示最多能选取的藏身点个数。 样例输入 Copy 4 4 1 2 3 2 3 4 4 2 样例输出 Copy 2 提示 对于20% 的数据,N≤10,M<=20。 对于60% 的数据, N≤100,M<=1000。 对于100% 的数据,N≤200,M<=30000,1<=x,y<=N。
代码:
#include <bits/stdc++.h> using namespace std; const int M = 30050; const int N = 10005; #define ri register int int n,m,vis[N],pipei[N]; bool mp[1010][1010]; bool dfs1(int u) { for(ri i=1;i<=n;i++) { if(!vis[i]&&mp[u][i]) { vis[i]=1; if(!pipei[i]||dfs1(pipei[i])) { pipei[i]=u; return 1; } } } return 0; } int main(){ scanf("%d%d",&n,&m); for(ri i=1;i<=m;i++) { int a,b; scanf("%d%d",&a,&b); mp[a][b]=1; } for(ri k=1;k<=n;k++) for(ri i=1;i<=n;i++) for(ri j=1;j<=n;j++) { if(k==i||i==j||j==k) continue; mp[i][j]=mp[i][j]||(mp[i][k]&&mp[k][j]); } int ans=0; for(ri i=1;i<=n;i++) { memset(vis,0,sizeof vis); if(dfs1(i)) ans++; } printf("%d\n",n-ans); return 0; }
二分图的带权的最大匹配 KM 算法 时间复杂度 n^4
pip 要初始化为-1;
#include <bits/stdc++.h> using namespace std; #define ri register int #define M 505 int n,m; struct edge{ int d,val; edge(){} edge (int a,int b){d=a,val=b;} }; vector <edge> p[M]; int vx[M],vy[M]; int vist[M]; int pip[M]; int xx; stack <int> q1; stack <int> q2; bool dfs(int a) { for(ri i=0;i<p[a].size();i++) { int b=p[a][i].d; if(vist[b]) continue; vist[b]=1; int t=vx[a]+vy[b]; if(t==p[a][i].val) // 这个顺序很重要 { if(pip[b]==-1||dfs(pip[b])) { pip[b]=a; q1.push(a); q2.push(b); return true; } }else xx=min(vx[a]-vy[b]-p[a][i].val,xx); } return false; } int main(){ memset(pip,-1,sizeof(pip)); //// remember scanf("%d%d",&n,&m); for(ri i=1;i<=m;i++) { int a,b,c; scanf("%d%d%d",&a,&b,&c); edge d;d=edge(b,c); p[a].push_back(d); } for(ri i=1;i<=n;i++) { int trmp = -19980739; for(ri j=0;j<p[i].size();j++) { trmp=max(trmp,p[i][j].val); } vx[i]=trmp; } for(ri i=1;i<=n;i++) { memset(vist,0,sizeof(vist)); xx=19980739; while(!dfs(i)) { memset(vist,0,sizeof(vist)); while(!q1.empty()) { int a=q1.top(); q1.pop(); vx[a]-=xx; } while(!q2.empty()) { int a=q2.top(); q2.pop(); vy[a]+=xx; } xx=19980739; } } long long ans=0; for(ri i=1;i<=n;i++) { ans+=vx[i]; ans+=vy[i]; } printf("%lld\n",ans); for(ri i=1;i<=n;i++) printf("%d ",pip[i]); return 0; }