ybtAu「图论」第1章 二分图匹配

A. 【例题1】消除格子

对每行和每列建点,如果一个格子有杂物,那么给对应的行和列连边,答案即该二分图的最小点覆盖,等于最大匹配。
由于边是单向的,所以列编号不 \(+n\) 问题也不大。

#include <iostream>
#define N 505
int n,m,vis[N],link[N],hed[N],tal[N*N],nxt[N*N],cnte,ans;
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
bool dfs(int x)
{
	for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]])
	{
		vis[tal[i]]=1;
		if(!link[tal[i]]||dfs(link[tal[i]])) return link[tal[i]]=x,1;
	}
	return 0;
}
int main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>m;
	for(int i=1,x,y;i<=m;i++) std::cin>>x>>y,adde(x,y);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++) vis[j]=0;
		if(dfs(i)) ans++;
	}
	std::cout<<ans;
}

B. 【例题2】攻击装置

发现每个马只能从黑点攻击到白点,或从白点攻击到黑点,所以对每个马的位置和它能攻击到的位置连边,所得的图是二分图。答案即最大独立集,即最小点覆盖的补集。

#include <iostream>
#include <cstring>
#define N 100005
#define F(x,y) ((x-1)*n+y)
int n,hed[N],tal[N],nxt[N],link[N],cnte,ans;
bool vis[N];
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
int dx[8]={-2,-2,-1,-1,1,1,2,2};
int dy[8]={-1,1,-2,2,-2,2,-1,1};
bool ac[505][505];
bool dfs(int x)
{
	for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]])
	{
		vis[tal[i]]=1;
		if(!link[tal[i]]||dfs(link[tal[i]])) return link[tal[i]]=x,1;
	}
	return 0;
}
int main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n;
	int cntx=0;
	for(int i=1;i<=n;i++)
	{
		std::string s;
		std::cin>>s;
		for(int j=0;j<n;j++) ac[i][j+1]=(s[j]=='0'),cntx+=(s[j]=='1');
	}
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(ac[i][j]&&(i+j&1))
		for(int k=0;k<8;k++)
		{
			int nx=i+dx[k],ny=j+dy[k];
			if(nx>=1&&nx<=n&&ny>=1&&ny<=n&&ac[nx][ny]) adde(F(i,j),F(nx,ny));
		}
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(ac[i][j]&&(i+j&1))
		memset(vis,0,sizeof vis),ans+=dfs(F(i,j));
	std::cout<<n*n-cntx-ans;
}

C. 【例题3】出租车订单

如果能在送完乘客 \(i\) 后去接乘客 \(j\),那么对 \(i\)\(j\) 连边,发现得到一个 DAG。答案即该 DAG 的最小路径覆盖。
DAG 的最小路径覆盖,相当于把每个点拆成两个点再连边,得到二分图,用总点数减去最大匹配数。
实际上在实现时不用真的拆点。

#include <iostream>
#include <cstring>
#define N 505
int n,g[N][N],vis[N],link[N];
std::pair<std::pair<int,int>,std::pair<int,int> > e[N];
std::pair<int,int> tim[N];
bool dfs(int x)
{
	for(int i=0;i<n;i++) if(g[x][i]&&!vis[i])
	{
		vis[i]=1;
		if(link[i]==-1||dfs(link[i])) return link[i]=x,1;
	}
	return 0;
}
int solve()
{
	int ans=0;
	for(int i=0;i<n;i++)
	{
		memset(vis,0,sizeof vis);
		if(dfs(i)) ans++;
	}
	return ans;
}
int main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	int T;
	std::cin>>T;
	while(T--)
	{
		memset(link,-1,sizeof link),memset(g,0,sizeof g),std::cin>>n;
		for(int i=0,a,b,c,d;i<n;i++)
		{
			std::string s;
			std::cin>>s>>a>>b>>c>>d;
			tim[i].first=((s[0]-'0')*10+s[1]-'0')*60+(s[3]-'0')*10+s[4]-'0';
			tim[i].second=tim[i].first+abs(a-c)+abs(b-d);
			e[i]={{a,b},{c,d}};
		}
		for(int i=0;i<n;i++) for(int j=i+1;j<n;j++)
			if(tim[i].second+abs(e[i].second.first-e[j].first.first)
			+abs(e[i].second.second-e[j].first.second)<tim[j].first) g[i][j]=1;
		std::cout<<n-solve()<<'\n';
	}
}

D. 游戏

撰写本篇时,由于金牌导航过于不可读,故参考洛谷题解
对网格图黑白染色,得到二分图,如果存在完美匹配那么先手必败,否则必胜,不在最大匹配上的点就是合法起始位置。

#include <iostream>
#include <algorithm>
#include <cstring>
#define F(x,y) ((x-1)*m+y-1)
#define N 105
#define M 100005
int n,m,ac[N][N],len,hed[N*N],tal[M],nxt[M],cnte,ct,link[M],vis[M],li[M],tp,flg[M];
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
int dx[4]={-1,0,0,1};
int dy[4]={0,-1,1,0};
bool dfs(int x)
{
	for(int i=hed[x];i;i=nxt[i]) if(vis[tal[i]]!=ct)
	{
		vis[tal[i]]=ct;
		if(link[tal[i]]==-1||dfs(link[tal[i]]))
			return link[tal[i]]=x,link[x]=tal[i],1;
	}
	return 0;
}
void dfs2(int x)
{
	vis[x]=ct;
	for(int i=hed[x];i;i=nxt[i])
		if(link[tal[i]]!=-1&&link[tal[i]]!=x&&vis[link[tal[i]]]!=ct)
			li[++tp]=link[tal[i]],dfs2(link[tal[i]]);
}
int main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>m;
	memset(link,-1,sizeof link);
	for(int i=1;i<=n;i++)
	{
		std::string s;
		std::cin>>s;
		for(int j=0;j<m;j++) if(s[j]=='.') ac[i][j+1]=1,len++;
	}
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if((i+j&1)&&ac[i][j]) for(int k=0;k<4;k++)
	{
		int nx=i+dx[k],ny=j+dy[k];
		if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&ac[nx][ny]) adde(F(i,j),F(nx,ny)),adde(F(nx,ny),F(i,j));
	}
	int ans=0;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
		if((i+j&1)&&link[F(i,j)]==-1) ct++,ans+=dfs(F(i,j));
	if(len%2==0&&ans==len/2) return std::cout<<"LOSE\n",0;
	std::cout<<"WIN\n";
	ct++;
	for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(link[F(i,j)]==-1&&ac[i][j])
		li[++tp]=F(i,j),dfs2(F(i,j));
	std::sort(li+1,li+tp+1);
	for(int i=1;i<=tp;i++) std::cout<<li[i]/m+1<<' '<<li[i]%m+1<<'\n';
}

E. 模板集合

由于给出的每个串最多只有一个星号,所以可以把一个带星号的串拆成两个不带星号的串。
如果两个串只有一位不同,那么对它们连边。答案即最大独立集,即最小点覆盖的补集。
由于未划分二分图,跑匈牙利算法得到的每个匹配会计算两次,所以要 \(\div2\)

#include <iostream>
#include <cstring>
#include <algorithm>
#define N 2005
int n,m,len,hed[N],tal[N*N],nxt[N*N],cnte,link[N],vis[N],ct;
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
bool ex[N];
bool cstar(std::string s) {for(int i=0;i<n;i++) if(s[i]=='*') return 1;return 0;}
bool dfs(int x)
{
	for(int i=hed[x];i;i=nxt[i]) if(vis[tal[i]]!=ct)
	{
		vis[tal[i]]=ct;
		if(!link[tal[i]]||dfs(link[tal[i]])) return link[tal[i]]=x,1;
	}
	return 0;
}
int main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	for(;;)
	{
		memset(hed,0,sizeof hed),memset(link,0,sizeof link),memset(ex,0,sizeof ex);
		for(;cnte;cnte--) tal[cnte]=nxt[cnte]=0;
		std::cin>>n>>m,len=0;
		if(!n&&!m) return 0;
		for(int i=1;i<=m;i++)
		{
			std::string s;
			std::cin>>s;
			if(cstar(s))
			{
				int t1=0,t2=0;
				for(int j=0;j<n;j++)
				{
					if(s[j]=='1') t1|=1<<j,t2|=1<<j;
					if(s[j]=='*') t1|=1<<j;
				}
				ex[t1]=ex[t2]=1;
			}
			else
			{
				int t1=0;
				for(int j=0;j<n;j++) t1|=s[j]-'0'<<j;
				ex[t1]=1;
			}
		}
		for(int i=0;i<(1<<n);i++) if(ex[i])
		{
			len++;
			for(int j=0;j<n;j++) if(ex[i^(1<<j)]) adde(i,i^(1<<j));
		}
		int ans=0;
		for(int i=0;i<(1<<n);i++) ct++,ans+=dfs(i);
		std::cout<<len-ans/2<<'\n';
	}
}

F. 祭祀

不会证明,但是求传递闭包之后跑最小可重链覆盖即可。

#include <iostream>
#include <cstring>
#define N 205
int n,m,hed[N],tal[N*N],nxt[N*N],cnte,link[N];
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
bool d[N][N],vis[N];
bool dfs(int x)
{
	for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]])
	{
		vis[tal[i]]=1;
		if(!link[tal[i]]||dfs(link[tal[i]])) return link[tal[i]]=x,1;
	}
	return 0;
}
int main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>m;
	for(int i=1,u,v;i<=m;i++) std::cin>>u>>v,d[u][v]=1;
	for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[i][j]|=d[i][k]&d[k][j];
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(d[i][j]) adde(i,j+n);
	int ans=0;
	for(int i=1;i<=n;i++) memset(vis,0,sizeof vis),ans+=dfs(i);
	std::cout<<n-ans;
}

G. 消毒问题

如果问题是二维的那么就是本章的 A 题,然而这是三维的。但是通过人类智慧,由 \(abc\le5000\) 可知至少有一个维度不大于 \(17\),于是可以暴力枚举这一维,对剩下两维做二分图最小点覆盖即可。

#include <iostream>
#include <cstring>
#define N 5005
int X[N],Y[N],Z[N],link[N],vis[N],ct,idx,hed[N],tal[N*N],wt[N*N],nxt[N*N],cnte,ac[N],ans,a,b,c;
void adde(int u,int v,int w) {tal[++cnte]=v,wt[cnte]=w,nxt[cnte]=hed[u],hed[u]=cnte;}
bool dfs(int x)
{
	for(int i=hed[x];i;i=nxt[i]) if(vis[tal[i]]!=ct&&!ac[wt[i]])
	{
		vis[tal[i]]=ct;
		if(!link[tal[i]]||dfs(link[tal[i]])) return link[tal[i]]=x,1;
	}
	return 0;
}
int solve()
{
	for(int i=1;i<=c;i++) link[i]=0;
	int ret=0;
	for(int i=1;i<=b;i++) ct++,ret+=dfs(i);
	return ret;
}
void dfs1(int x,int cc)
{
	if(x>a) return (void)(ans=std::min(ans,cc+solve()));
	ac[x]=1,dfs1(x+1,cc+1);
	ac[x]=0,dfs1(x+1,cc);
}
int main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	int T;
	std::cin>>T;
	while(T--)
	{
		memset(hed,0,sizeof hed);
		for(;cnte;cnte--) tal[cnte]=wt[cnte]=nxt[cnte]=0;
		int fg=0;
		idx=0;
		std::cin>>a>>b>>c;
		if(a<b&&a<c) fg=0;
		if(b<a&&b<c) fg=1;
		if(c<a&&c<b) fg=2;
		for(int i=1;i<=a;i++) for(int j=1;j<=b;j++) for(int k=1,ff;k<=c;k++)
		{
			std::cin>>ff;
			if(ff) X[++idx]=i,Y[idx]=j,Z[idx]=k;
		}
		if(fg==1) std::swap(b,a);
		if(fg==2) std::swap(c,a);
		for(int i=1;i<=idx;i++)
		{
			if(fg==1) std::swap(Y[i],X[i]);
			if(fg==2) std::swap(Z[i],X[i]);
		}
		for(int i=1;i<=idx;i++) adde(Y[i],Z[i],X[i]);
		ans=1e9,dfs1(1,0);
		std::cout<<ans<<'\n';
	}
}
posted @ 2025-06-10 15:14  整齐的艾萨克  阅读(14)  评论(0)    收藏  举报