【300iq Contest 1 H】Hall‘s Theorem 题解

题目大意

  一个二分图,设左边的一个点集为 SS,记它在右边的邻集为 N(S)N(S),如果 S>N(S)|S|>|N(S)|,则称 SS 是 critical 的。
  给定 n,kn,k,构造一幅左右各 nn 个点的二分图,使得 critical 的点集数量恰好为 kk

  n<20, 0k<2nn < 20,\ 0 \le k < 2^n
  1s

题解

  (虽然题解提到杨表但是并不能理解如何用杨表解释这个题 qaq
  不过杨表的结构却是很有启示性。

  先把 kk 换成非空非 critical 点集数 2n1k2^n-1-k。假设左边每个点 ii 都连向右边的一个前缀 1,,ai1,\cdots,a_i,那么左边的每一个点集只取决于其 aia_i 最大的点。把左边的点按 aia_i 从小到大排序,那么一个点 ii 的贡献就是 j=0ai1(i1j1)\sum_{j=0}^{a_i-1} \binom{i-1}{j-1}
  那么可以看成有一个 n2n^2 的网格图,元素 (i,j)(i,j) 的价值是 (i1j1)\binom{i-1}{j-1}。现在每一行选一个前缀,每一行的长度要大于等于上一行,问是否存在一种方案使得价值和为 kk

  题解给的标准做法是 O(n32n64)O(\frac{n^32^n}{64}) dp,设 dpi,jdp_{i,j} 表示目前到第 ii 行,这行选的前缀长度为 jj,所能取到的价值和的 bitset。
  但是观察一波发现可以从下往上贪心。从最后一行开始,每行从左到右,能选则选,不能选则往上一行。
  不会证明,只能马后炮地归纳证明一下。归纳证明左上角为 (1,1)(1,1) 右下角为 (n,m)(n,m) 的子矩阵可以取到所有不超过矩阵元素和的价值和。n=1n=1 时显然满足。n>1n>1 时,要么最后一行取满了,那么剩余价值和一定不超过去掉最后一行的子矩阵的元素和;要么最后一行没取满,只取到 (n,j)(n,j),则剩余价值和不超过 valuen,j+1=(n1j)value_{n,j+1}=\binom{n-1}{j},而 i=0n2(ij1)=(n1j)\sum_{i=0}^{n-2} \binom{i}{j-1}=\binom{n-1}{j},所以剩余价值和也不超过去掉最后一行的子矩阵的元素和。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

const int maxn=25;

int n,k;
vector<pair<int,int>> e;

int C[maxn][maxn];
void C_Pre(int n)
{
fo(i,0,n)
{
C[i][0]=1;
fo(j,1,i) C[i][j]=C[i-1][j-1]+C[i-1][j];
}
}

int main()
{
scanf("%d %d",&n,&k);
k=(1<<n)-k-1;

C_Pre(n);

int cur=0;
fd(i,n,1)
fo(j,1,n)
if (cur+C[i-1][j-1]>k) break; else
{
cur+=C[i-1][j-1];
e.push_back(make_pair(i,j));
}

printf("%d\n",e.size());
for(auto p:e) printf("%d %d\n",p.first,p.second);
}