题意:给 n×mn\times mn×m 的表格填入 [1,max(n,m)][1,\max(n,m)][1,max(n,m)] 的数,每行每列不能重复,且字典序最小。
n,m≤50n,m\leq 50n,m≤50
数据范围很小,所以是多项式就能过。
考虑每个位置从小到大依次填值,判断之后是否有合法解。
当 n≤mn\leq mn≤m 时所有数和没用过的列做二分图匹配就可以了。但 n>mn>mn>m 时就变成了三分图匹配,没法做。
然后是个结论:当 n>mn>mn>m 时,填了 xxx 行,问题有解当且仅当每个数还需要填的个数不超过剩余行数。显然每个数必须恰好填 nnn 个,所以这个“需要填的个数”是确定的。
必要性显然,充分性后述。
考虑每一行,有一些数是这一列必须匹配的,其余数可以匹配也可以不匹配。可以用匈牙利限制顺序或上下界网络流解决。
现在证明一定存在满匹配。考虑 Hall 定理,左边是所有数,右边是所有列,有连边当且仅当这个数没在这列出现过。我们任意选择 iii 列,有满匹配的充要条件是这 iii 列对应的点的邻边的交集大小 ≥k\geq k≥k。假设交集小于 kkk,那么一定有一种数在后面出现了 >ixi=x>\dfrac{ix}{i}=x>iix=x 次,其中 xxx 为剩余行数,与已知矛盾,故得证。
然后直接跑就行了。复杂度 O(poly(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;
}