【TC10738】TheContest【Hall 定理】【贪心】【二分图匹配】

题意:给 n × m n\times m n×m 的表格填入 [ 1 , max ⁡ ( n , m ) ] [1,\max(n,m)] [1,max(n,m)] 的数,每行每列不能重复,且字典序最小。

n , m ≤ 50 n,m\leq 50 n,m50

数据范围很小,所以是多项式就能过。

考虑每个位置从小到大依次填值,判断之后是否有合法解。

n ≤ m n\leq m nm 时所有数和没用过的列做二分图匹配就可以了。但 n > m n>m n>m 时就变成了三分图匹配,没法做。

然后是个结论:当 n > m n>m n>m 时,填了 x x x 行,问题有解当且仅当每个数还需要填的个数不超过剩余行数。显然每个数必须恰好填 n n n 个,所以这个“需要填的个数”是确定的。

必要性显然,充分性后述。

考虑每一行,有一些数是这一列必须匹配的,其余数可以匹配也可以不匹配。可以用匈牙利限制顺序或上下界网络流解决。

现在证明一定存在满匹配。考虑 Hall 定理,左边是所有数,右边是所有列,有连边当且仅当这个数没在这列出现过。我们任意选择 i i i 列,有满匹配的充要条件是这 i i i 列对应的点的邻边的交集大小 ≥ k \geq k k。假设交集小于 k k k,那么一定有一种数在后面出现了 > i x i = x >\dfrac{ix}{i}=x >iix=x 次,其中 x x x 为剩余行数,与已知矛盾,故得证。

然后直接跑就行了。复杂度 O ( p o l y ( n , m ) ) \Omicron(poly(n,m)) O(poly(n,m))

代码的题不太一样,仅供参考

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <queue>
#define MAXN 65
#define MAXM 2077
using namespace std;
const int INF=0x7fffffff;
struct edge{int u,v,c;}e[MAXM];
int head[MAXN],cur[MAXN],nxt[MAXM],cnt=1;
inline void insert(int u,int v,int c){e[++cnt]=(edge){u,v,c};nxt[cnt]=head[u];head[u]=cnt;}
inline void addnode(int u,int v,int c){insert(u,v,c),insert(v,u,0);}
int dis[MAXN],S,T,SS,TT;
bool bfs(int S,int T)
{
	queue<int> q;
	q.push(T);
	memset(dis,-1,sizeof(dis));
	dis[T]=0;
	while (!q.empty())
	{
		int u=q.front();q.pop();
		for (int i=head[u];i;i=nxt[i])
			if (e[i^1].c&&dis[e[i].v]==-1)
			{
				dis[e[i].v]=dis[u]+1;
				q.push(e[i].v);
				if (e[i].v==S) return true;
			}
	}
	return false;
}
int dfs(int u,int f,int T)
{
	if (u==T||!f) return f;
	int used=0;
	for (int& i=cur[u];i;i=nxt[i])
		if (e[i].c
		&&dis[u]==dis[e[i].v]+1)
		{
			int w=dfs(e[i].v,min(e[i].c,f),T);
			e[i^1].c+=w,e[i].c-=w;
			used+=w,f-=w;
			if (!f) break;
		}
	if (!used) dis[u]=-1;
	return used;
}
inline int dinic(int S,int T){int ans=0;while (bfs(S,T)) memcpy(cur,head,sizeof(cur)),ans+=dfs(S,INF,T);return ans;}
inline void clear(){memset(head,0,sizeof(head));for (int i=2;i<=cnt;i++) nxt[i]=0;cnt=1;}
int a[MAXN][MAXN],vis[MAXN][MAXN],now[MAXN],res[MAXN];
int n,m,lim,vip[MAXN],tot;
bool check(int y)
{
	clear();
	int sum=0;
	for (int i=1;i<=lim;i++) 
		if (!now[i]) 
		{
			if (vip[i]) addnode(SS,i,1),++sum;
			else addnode(S,i,1);
		}
	addnode(T,S,INF),addnode(S,TT,sum);
	for (int i=y+1;i<=m;i++) addnode(i+lim,T,1);
	for (int i=1;i<=lim;i++)
		if (!now[i])
			for (int j=y+1;j<=m;j++)
				if (!vis[j][i])
					addnode(i,j+lim,1);
	if (dinic(SS,TT)<sum) return false;
	return dinic(S,T)==m-y;
}
int main()
{
	for (n=1;n<=30;n++)
		for (m=1;m<=30;m++)
		{
			lim=max(n,m);
			S=2*lim+1,T=S+1,SS=T+1,TT=SS+1;
			for (int i=1;i<=lim;i++) res[i]=n+m-lim;
			for (int i=1;i<=n;i++)
			{
				for (int j=1;j<=lim;j++) vip[j]=(res[j]==n-i+1); 
				for (int j=1;j<=m;j++)
					while (++a[i][j])
					{
						if (now[a[i][j]]||vis[j][a[i][j]]) continue;
						now[a[i][j]]=1,vis[j][a[i][j]]=1;
						if (check(j)) break;
						now[a[i][j]]=0,vis[j][a[i][j]]=0;
					}
				for (int j=1;j<=m;j++) --res[a[i][j]];
				memset(now,0,sizeof(now));	
			}
			for (int i=1;i<=n;i++,puts(""))
				for (int j=1;j<=m;j++)
					printf("%d ",a[i][j]);
			puts("");
			memset(a,0,sizeof(a)),memset(vis,0,sizeof(vis));		
		}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值