分享

二分图

 头号码甲 2022-12-04 发布于北京

定义

二分图:可以把点集分为两部分\(A,B\),其中\(A\)\(B\)内部不存在连边的图
二分图的确立,当且仅当图中不存在奇环
Pf:黑白染色

概念:

  1. 匹配:\(A,B\)一一对应,不存在相邻的边的边集
  2. 点覆盖:点集\(S\)满足每条边至少存在一点位于\(S\)
  3. 独立集:点集\(S\)满足每条边至多存在一点位于\(S\)
  4. 边覆盖:边集\(E\)满足每个顶点至少存在一条边位于\(E\)
  5. 路径覆盖(链):在图中找路径使得每个节点在一条路径上
  6. 团:点集\(S\)满足其中所有点都相互连通
  7. 反链:点集\(S\)满足\(S\)中所有点互不联通

匈牙利

求二分图中最大匹配

bool dfs(int u) {
	for(auto v:V[u]) {
		if(fl[v]!=lim) {
			fl[v]=lim;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}

时间复杂度\(O(nm)\)

増广路


(虚边表示非匹配边,实边表示匹配边)
其中最后图上的増广路必须满足匹配边>=非匹配边(否则会有空余的边,这不是最大匹配)
特殊的情况:从非匹配点开始走到的都是匹配点,非匹配边-匹配边交叉(正好为偶数条边)

常见关系:

|最大匹配|=|最小点覆盖|

Pf:如果除最大匹配外还存在某条边没被覆盖,显然匹配数应+1,所以最大匹配是点覆盖,故|最大匹配|>=|最小点覆盖|
匹配的定义告诉我们,覆盖的边和边之间没有交点,所以不可能存在更小的点覆盖,即证

最小点覆盖的方案构造:先求出最大匹配,从左边未匹配点开始,走未匹配边到右边匹配点(一定是),走匹配边到左边的匹配点...沿着路径打标记
左边点集中未出现的点和右边点集中出现的点是最小点覆盖
说明:一个图可以被分成若干条増广路,而増广路有两种情况

前者(存在未匹配点)统计右节点优,后者为了方便统计左节点

点覆盖与独立集对应互补,最大独立集=G-最小点覆盖

Pf:由定义可以发现

对于不存在孤立点的图,|最大匹配|+|最小边覆盖|=|V|

Pf:不妨设节点数为\(n\),最大匹配数为\(m\)
一条边至多覆盖2个节点,所以显然要将匹配边全覆盖,之后剩下的节点只能有一条边逐一覆盖
\(2m+t=n,m+t=|\text{最小边覆盖}|\),即证
构造:同上

最大团=补图的最大独立集

Pf 定义

|最小不可重链覆盖|=n-|最大匹配|

Pf:开始时每个点裂成入点和出点,把每个点看作一条链,每次匹配入点和出点(即连边合并),链会减1,即证

最小可重链覆盖可以转化为最小不可重链覆盖

将点\(i\)能到达的所有点都与\(i\)相连即可(最优的情况下,每个点只需要经过一次)

|最长反链|=|最小可重链覆盖|

最长反链方案构造:先按上述求出最大独立集,再求出入点和出点都在最大独立集上的点

解题思路:

难点往往在构造二分图
通常行列,或者没有奇环的图。。。

例题1

消除格子
行列分开,求最小点覆盖,等于最大匹配

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

const int N=1005,M=505*505;
int n,K,to[M],nxt[M],he[N],cnt,ma[N];
bool fl[N];

inline void add(int u,int v) {
	to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}

bool dfs(int u) {
	for(int e=he[u];e;e=nxt[e]) {
		int v=to[e];
		if(!fl[v-n]) {
			fl[v-n]=1;
			if(!ma[v-n]||dfs(ma[v-n])) {
				ma[v-n]=u; return 1;
			} 
		}
	}
	return 0;
}
int main(){
	scanf("%d%d",&n,&K); 
	for(int i=1;i<=K;i++) {
		int x,y; scanf("%d%d",&x,&y);
		add(x,y+n);
	}
	int ans=0;
	for(int i=1;i<=n;i++) {
		memset(fl,0,sizeof(fl));
		if(dfs(i)) ans++;
	}
	printf("%d\n",ans);
	return 0;
}

例题2

出租车订单
根据能否连载连边,然后求最小不可重链覆盖
注意>=1这种细节

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

const int N=505;
int n,a[N],b[N],c[N],d[N],t[N],ma[N];
bool fl[N];
char s[10];
vector<int>V[N];

bool dfs(int u) {
	for(auto v:V[u]) {
		if(!fl[v]) {
			fl[v]=1;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}
int main(){
	int T; scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=1;i<=n;i++) {
			scanf("%s%d%d%d%d",s+1,&a[i],&b[i],&c[i],&d[i]);
			int h=(s[1]-'0')*10+s[2]-'0',m=(s[4]-'0')*10+s[5]-'0';
			t[i]=h*60+m;
		}
		for(int i=1;i<=n;i++) {
			int tmp=t[i]+abs(c[i]-a[i])+abs(b[i]-d[i]);
			V[i].clear();
			for(int j=1;j<=n;j++) {
				if(i!=j&&t[j]-tmp>=abs(a[j]-c[i])+abs(b[j]-d[i])+1) {
					V[i].push_back(j);
				}
			}
		}
		memset(ma,0,sizeof(ma));
		int ans=n;
		for(int i=1;i<=n;i++) {
			memset(fl,0,sizeof(fl));
			if(dfs(i)) ans--;
		}
		printf("%d\n",ans);
	}
	return 0;
}

例题3

Luogu P1129 [ZJOI2007] 矩阵游戏
首先发现横纵互不干扰(随意换顺序)
接着发现横纵分开后只需要横不需要纵,
最后发现只要不同列都有不同行的1即可
所以行和列拆开,判断最大匹配是否为\(n\)

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

const int N=505;
int n,ma[N];
bool fl[N];
vector<int>V[N];

bool dfs(int u) {
	for(auto v:V[u]) {
		if(!fl[v]) {
			fl[v]=1;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}
int main(){
	int T; scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=1;i<=n;i++) {
			V[i].clear();
			for(int j=1;j<=n;j++) {
				int op; scanf("%d",&op);
				if(op) V[i].push_back(j);
			}
		}
		memset(ma,0,sizeof(ma));
		int ans=0;
		for(int i=1;i<=n;i++) {
			memset(fl,0,sizeof(fl));
			if(dfs(i)) ans++;
		}
		if(ans==n) puts("Yes");
			else puts("No");
	}
	return 0;
}

例题5

Luogu P4136 谁能赢呢?
把图拆成\(1\times 2\)的骨牌分组,判断什么时候是先手开骨牌的头,还是后手开骨牌的头,谁开骨牌头谁输
可以理解成有一副二分图,如果某人先手时不巧从未匹配点开始,他便输了,因为要么之后没有点,要么之后所有点都完美匹配了,后手只需要沿着匹配边走即可

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

int n;
int main(){
	scanf("%d",&n);
	while(n) {
		if(n&1) puts("Bob");
			else puts("Alice");
		scanf("%d",&n);
	}
	puts("");
}

例题6

Luogu P4055 [JSOI2009]游戏
多了限制,另外让你求初始点
由上题可以发现,先手下在某个最大匹配的未匹配点上,之后后手无论往哪儿走,始终落在存在匹配边的匹配点上,先手只需要沿着匹配边走即可
而如果先手下的位置一定是匹配点,那么后手只需要沿匹配边走即可,因为先手无论如何都会落在存在匹配边的匹配点上
所以考虑如何找最大匹配的未匹配点求并
只需要从每个未匹配点开始访问増广路,访问到的节点都可以成为最大匹配的未匹配点

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

const int N=10005;
int n,m,ma[N];
bool fl[N],ans[N];
char s[105][105];
vector<int>V[N];

inline int id(int i,int j) {
	return (i-1)*m+j;
}
bool dfs(int u) {
	for(auto v:V[u]) {
		if(!fl[v]) {
			fl[v]=1;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}

void dgs(int u) {
	ans[u]=1;
	for(auto v:V[u]) {
		if(!fl[v]) {
			fl[v]=1; dgs(ma[v]);
		}
	}
}
int main() {	
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%s",s[i]+1); 
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(i+j&1&&s[i][j]=='.') {
				if(s[i-1][j]=='.') {
					V[id(i,j)].push_back(id(i-1,j));
				}
				if(s[i][j-1]=='.') {
					V[id(i,j)].push_back(id(i,j-1));
				}
				if(s[i+1][j]=='.') {
					V[id(i,j)].push_back(id(i+1,j));
				}
				if(s[i][j+1]=='.') {
					V[id(i,j)].push_back(id(i,j+1));
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) {
			if(i+j&1&&s[i][j]=='.') {
				memset(fl,0,sizeof(fl));
				dfs(id(i,j));
			}
		}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			V[id(i,j)].clear();
			if(!(i+j&1)&&ma[id(i,j)]) {
				ma[ma[id(i,j)]]=id(i,j);
			}
		}
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(i+j&1&&s[i][j]=='.') {
				if(s[i-1][j]=='.'&&id(i-1,j)!=ma[id(i,j)]) {
					V[id(i,j)].push_back(id(i-1,j));
					V[id(i-1,j)].push_back(id(i,j));
				}
				if(s[i][j-1]=='.'&&id(i,j-1)!=ma[id(i,j)]) {
					V[id(i,j)].push_back(id(i,j-1));
					V[id(i,j-1)].push_back(id(i,j));
				}
				if(s[i+1][j]=='.'&&id(i+1,j)!=ma[id(i,j)]) {
					V[id(i,j)].push_back(id(i+1,j));
					V[id(i+1,j)].push_back(id(i,j));
				}
				if(s[i][j+1]=='.'&&id(i,j+1)!=ma[id(i,j)]) {
					V[id(i,j)].push_back(id(i,j+1));
					V[id(i,j+1)].push_back(id(i,j));
				}
			}
		}
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			memset(fl,0,sizeof(fl));
			if(s[i][j]=='.'&&!ma[id(i,j)]) {
				dgs(id(i,j));
			} 
		}
	}
	bool fll=0;
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(ans[id(i,j)]) {
				if(!fll) puts("WIN");
				fll=1;
				printf("%d %d\n",i,j);
			}
		}
	}
	if(!fll) puts("LOSE");
	return 0;
}

例题7

Luogu P2172 [国家集训队]部落战争
先来证明:像马一样在网格图上跳跃形成的轨迹为二分图,即只存在偶环
Pf: 假设从\((0,0)\)开始,每次\(+-(a,b)\),\(+-(b,a),a>0,b>0\)
不妨设\(x(a,b)+y(b,a)=(0,0)\),则

\[\left\{ \begin{array}{c} ax+by=0 \\ bx+ay=0 \end{array} \right. \]

相加发现:

\[(a+b)(x+y)=0 \]

则:

\[x+y=0 \]

所以只存在偶环
求最小不可重路径覆盖即可

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

const int N=5005;
int n,m,r,c,ma[N];
bool fl[N];
char s[N][N];
vector<int>V[N];

inline int id(int x,int y) {
	return (x-1)*m+y;
}

bool dfs(int u) {
	for(auto v:V[u]) {
		if(!fl[v]) {
			fl[v]=1;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}

int main() {	
	scanf("%d%d%d%d",&n,&m,&r,&c);
	for(int i=1;i<=n;i++){
		scanf("%s",s[i]+1);
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(s[i][j]=='.'){
				if(s[i+r][j+c]=='.') {
					V[id(i,j)].push_back(id(i+r,j+c));
				}
				if(s[i+c][j+r]=='.'){
					V[id(i,j)].push_back(id(i+c,j+r));
				}
				if(j-c>0&&s[i+r][j-c]=='.') {
					V[id(i,j)].push_back(id(i+r,j-c));
				}
				if(j-r>0&&s[i+c][j-r]=='.'){
					V[id(i,j)].push_back(id(i+c,j-r));
				}
				
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++) {
			if(s[i][j]=='.') {
				ans++;
				memset(fl,0,sizeof(fl));
				if(dfs(id(i,j))) ans--;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}

例题8

幻灯片
建立二分图后判断是否可以有多解
法1:暴力删除边后判断答案是否相同
法2:对每条匹配边建反边(提供反悔机会),在残余网络上查找是否存在环,如果存在说明有多解
有向图判断具体的环,只能tarjan

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

const int N=505;
int n,ma[N],to[N],x[N],y[N],x1[N],t1[N],x2[N],y2[N],dfn[N],low[N],st[N],top,co[N],col,cnt;
bool fl[N];
vector<int>V[N];

bool dfs(int u) {
	for(auto v:V[u]) {
		if(!fl[v]) {
			fl[v]=1;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}

void tar(int u) {
	dfn[u]=low[u]=++cnt; st[++top]=u;
	for(auto v:V[u]) {
		if(!dfn[v]) {
			tar(v),low[u]=min(low[u],low[v]);
		} else if(!co[v]) {
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(dfn[u]==low[u]) {
		co[u]=++col;
		while(st[top]!=u) {
			co[st[top]]=col; top--;
		}
		top--;
	}
}
int main(){
	scanf("%d",&n); int id=0;
	while(n) {
		printf("Heap %d\n",++id);
		for(int i=1;i<=n;i++) {
			scanf("%d%d%d%d",&x1[i],&x2[i],&t1[i],&y2[i]);
		}
		for(int i=1;i<=n;i++) {
			scanf("%d%d",&x[i],&y[i]);
			V[i].clear();
			for(int j=1;j<=n;j++) {
				if(x1[j]<x[i]&&x[i]<x2[j]&&t1[j]<y[i]&&y[i]<y2[j]) {
					V[i].push_back(j);
				}
			}
		}
		memset(ma,0,sizeof(ma));
		int ans=0;
		for(int i=1;i<=n;i++) {
			memset(fl,0,sizeof(fl));
			if(dfs(i)) ans++;
		}
		/*if(ans!=n) {
			puts("none");
		} else */{
			for(int i=1;i<=n+n;i++) {
				V[i].clear();
			}
			for(int i=1;i<=n;i++) {
				to[ma[i]]=i; V[i+n].push_back(ma[i]);
			}
			for(int i=1;i<=n;i++) {
				for(int j=1;j<=n;j++) {
					if(to[i]!=j&&x1[j]<x[i]&&x[i]<x2[j]&&t1[j]<y[i]&&y[i]<y2[j]) {
						V[i].push_back(j+n);
					}
				}
			}
			memset(dfn,0,sizeof(dfn));
			memset(co,0,sizeof(co));
			col=0; top=0; cnt=0;
			for(int i=1;i<=n+n;i++) {
				if(!dfn[i]) tar(i);
			}
			bool fll=0;
			for(int i=1;i<=n;i++) {
				if(co[ma[i]]!=co[i+n]) {
					printf("(%c,%d) ",i+'A'-1,ma[i]);
					fll=1;
				}
			}
			if(!fll) {
				puts("none");
			} else puts("");
		}
		scanf("%d",&n);
	}
	return 0;
}

例题9

模板集合
只能有一个“*”,所以将所有可能的01子串处理出
将可能合并的子串用边相连,只会有一个位置不同,所以可以按01子串中1的个数的奇偶性分成二分图

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

const int N=5005;
int n,m,ma[N];
bool fl[N],num[N],t[N];
char s[N];
vector<int>V[N];

inline int id(int x,int y) {
	return (x-1)*m+y;
}

bool dfs(int u) {
	for(auto v:V[u]) {
		if(!fl[v]) {
			fl[v]=1;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}

int main() {	
scanf("%d%d",&m,&n);
for(int i=1;i<=1023;i++) {
	num[i]=num[i>>1]^(i&1);
}
while(n||m) {
	memset(t,0,sizeof(t));
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		int t1=0,t2=-1;
		for(int j=1;j<=m;j++) {
			if(s[j]=='*') {
				t1<<=1,t2=t1|1;
			} else {
				t1=(t1<<1)|(s[j]=='1');
				if(t2!=-1) {
					t2=(t2<<1)|(s[j]=='1');
				}
			}
		}
		t[t1]=1;
		if(t2!=-1) t[t2]=1;
	}
	for(int i=1;i<=1023;i++) {
		V[i].clear();
	}
	for(int i=0;i<=1023;i++){
		if(t[i])
		for(int j=1;j<=m;j++) {
			if((i&(1<<j-1))==0) {
				int k=i|(1<<j-1);
				if(t[k]) {
					if(num[i]) {
						V[i].push_back(k);
					} else {
						V[k].push_back(i);
					}
				}
			}
		}
	}
	int ans=0;
	memset(ma,0,sizeof(ma));
	for(int i=0;i<=1023;i++){
		if(t[i]) ans++;
		if(t[i]&&num[i]) {
			memset(fl,0,sizeof(fl));
			if(dfs(i)) ans--;
		}
	}
	printf("%d\n",ans);
scanf("%d%d",&m,&n);
}
	return 0;
}

例题10

男孩女孩
求最大团

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

const int N=205;
int n,m,K,f[N][N],ma[N];
bool fl[N];
vector<int>V[N];

bool dfs(int u) {
	for(auto v:V[u]) {
		if(!fl[v]) {
			fl[v]=1;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}

int main(){
	scanf("%d%d%d",&n,&m,&K); int id=0; 
while(n||m||K) {
	id++; printf("Case %d: ",id);
	memset(f,0,sizeof(f));
	for(int i=1;i<=K;i++) {
		int u,v; scanf("%d%d",&u,&v);
		f[u][v]=1;
	}
	for(int i=1;i<=n;i++) {
		V[i].clear();
		for(int j=1;j<=m;j++) {
			if(!f[i][j]) {
				V[i].push_back(j);
			}
		}
	}
	memset(ma,0,sizeof(ma));
	int ans=n+m;
	for(int i=1;i<=n;i++) {
		memset(fl,0,sizeof(fl));
		if(dfs(i)) ans--;
	}
	printf("%d\n",ans);
	scanf("%d%d%d",&n,&m,&K);
}
return 0;
}

例题 11

Luogu P4298 [CTSC2008]祭祀
最长反链=最小可重路径覆盖
第一问,第二问,KO
第三问:将该点和其关的所有点删掉,如果该点可能成为祭点,则反链数量会减少1,再来一遍即可

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

const int N=105;
int n,m,f[N][N],ma[N];
bool fl[N],vis[N<<1],to[N],ban[N];
vector<int>V[N],V1[N];

bool dfs(int u) {
	if(ban[u]) return 0;
	for(auto v:V[u]) {
		if(!ban[v]&&!fl[v]) {
			fl[v]=1;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}

void dgs(int u) {
	vis[u]=1;
	for(auto v:V[u]) {
		if(!fl[v]){
			fl[v]=1;
			vis[v+n]=1;
			dgs(ma[v]);
		}
	}
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) {
		int u,v; scanf("%d%d",&u,&v);
		f[u][v]=1;
	}
	for(int k=1;k<=n;k++) {
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=n;j++) {
				if(f[i][k]&&f[k][j]) f[i][j]=1;
			}
		}
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			if(f[i][j]) {
				V[i].push_back(j);
				V1[j].push_back(i);
			}
		}
	}
	int ans=n;
	for(int i=1;i<=n;i++) {
		memset(fl,0,sizeof(fl));
		if(dfs(i)) ans--;
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++) to[ma[i]]=1;
	for(int i=1;i<=n;i++) {
		memset(fl,0,sizeof(fl));
		if(!to[i]) dgs(i);
	}
	for(int i=1;i<=n;i++) {
		printf("%d",vis[i]&(!vis[i+n]));
	}
	puts("");
	for(int i=1;i<=n;i++) {
		int res=n;
		memset(ban,0,sizeof(ban));
		for(int j=1;j<=n;j++) {
			if(f[i][j]||f[j][i]||i==j) {
				ban[j]=1;
				res--;
			}
		}
		memset(ma,0,sizeof(ma));
		for(int i=1;i<=n;i++) {
			memset(fl,0,sizeof(fl));
			if(dfs(i)) res--;
		}
		printf("%d",res==ans-1);
	}
	return 0;
}

例题12

Luogu P3231 [HNOI2013]消毒
考虑2维:对于\((a,b)\)的代价\(min(a,b)\),可以看出消去\(a\)次行,或者消去\(b\)次列
所以可以将行列分开做,二分图
再加1维,没有三分图这种东西,发现最小的一维最大为17,所以暴力枚举最小一维怎么删的状态,将没被删的叠在一起,看作2维

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

const int N=5005;
int n,ma[N],lim,a[N],b[N],c[N],fl[N];
vector<int>V[N];

bool dfs(int u) {
	for(auto v:V[u]) {
		if(fl[v]!=lim) {
			fl[v]=lim;
			if(!ma[v]||dfs(ma[v])) {
				ma[v]=u; return 1;
			}
		}
	}
	return 0;
}

int main(){
//	freopen("1.in","r",stdin);
int T; scanf("%d",&T);
while(T--) { 
	scanf("%d%d%d",&a[0],&b[0],&c[0]);  
	int ans=1e9;  n=0;
	for(int i=1;i<=a[0];i++) {
		for(int j=1;j<=b[0];j++) {
			for(int k=1;k<=c[0];k++) {
				int op; scanf("%d",&op);
				if(op) {
					a[++n]=i,b[n]=j,c[n]=k;
				}
			}
		}
	}
	if(a[0]>=b[0]&&b[0]<=c[0]) swap(a,b);
	if(a[0]>=c[0]&&b[0]>=c[0]) swap(b,c);
	for(int i=0;i<(1<<a[0]);i++) {
		memset(fl,0,sizeof(fl));
		memset(ma,0,sizeof(ma));
		int res=0;
		for(int j=1;j<=n;j++) {
			if(!(i&(1<<a[j]-1))) V[b[j]].push_back(c[j]);
		}
		for(int j=1;j<=a[0];j++) {
			if(i&(1<<j-1)) res++;
		}
		for(int j=1;j<=b[0];j++) {
			lim=j;
			if(dfs(j)) res++;
		}
		ans=min(ans,res);
		for(int j=1;j<=n;j++) {
			V[b[j]].clear();
		}
	}
	printf("%d\n",ans);
}
return 0;
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多