Note - Hall 定理
与二分图有关的问题的解决思路之一。另一个是 flow。
Hall 定理:给定一个二分图,其中左部点集合为 \(L\),右部点集合为 \(R\)。该二分图存在大小为 \(L\) 的匹配,当且仅当 \(\forall S \subseteq L, |S| \le |N(S)|\)。
Hall 定理推论:给定一个二分图,其中左部点集合为 \(L\),右部点集合为 \(R\)。其最大匹配的大小为 \(|L| - \max_{S \subseteq L} |S|-|N(S)|\)。
不会证喵。
例题 1(Atcoder abc214_e):有 \(n (n \le 2 \times 10^5)\) 个左部点和 \(10^9\) 个右部点,每个左部点有边连向右部点 \(i \in [l, r]\)。判断是否存在一个大小为 \(n\) 的匹配。
发现图规模太大,并不能使用 flow。考虑 Hall 定理。然而 Hall 定理要遍历所有子集,真的 \(n!\) 遍历的话肯定是不行的。
发现如果 \(\exist S, S > N(S)\),那么 \(\exist S, S > N(S) \land N(S) 为一个连续区间\)。这个是容易证明的。
然后我们离散化所有有意义的右部点。枚举每个右部点,维护一棵线段树表示 \(|S| - |N(S)|\),每次加入右部点时线段树全局减去 \(pos_i - pos_{i-1}\),然后加入右部点对应的左部点即可。如果整个线段树的最大值大于 \(0\) 即无解。
然后没了、
#include <bits/stdc++.h>
#define llong long long
#define N 200005
using namespace std;
#define bs (1<<20)
char buf[bs], *p1, *p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,bs,stdin),p1==p2)?EOF:*p1++)
template<typename T>
inline void read(T& x){
x = 0; char ch = gc();
while(ch < '0' || ch > '9') ch = gc();
while(ch >= '0' && ch <= '9')
x = (x<<3)+(x<<1)+(ch^48), ch = gc();
}
template<typename T, typename... Args>
inline void read(T& x, Args&... y){
return read(x), read(y...);
}
int n;
struct Int{
int l, r;
};
Int a[N];
int tmp[N<<1], cnt;
int val[N<<3], tag[N<<3];
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define mid ((l+r)>>1)
inline void addtag(int x, int k){
val[x] += k, tag[x] += k;
return;
}
inline void pushdown(int x){
if(!tag[x]) return;
addtag(ls(x), tag[x]);
addtag(rs(x), tag[x]);
tag[x] = 0;
return;
}
inline void modify(int L, int R, int k, int x = 1, int l = 1, int r = cnt){
if(L > R) return;
if(L <= l && R >= r) return addtag(x, k);
pushdown(x);
if(L <= mid) modify(L, R, k, ls(x), l, mid );
if(R > mid) modify(L, R, k, rs(x), mid+1, r);
val[x] = max(val[ls(x)], val[rs(x)]);
return;
}
inline int query(int L, int R, int x = 1, int l = 1, int r = cnt){
if(L <= l && R >= r) return val[x];
pushdown(x);
if(R <= mid) return query(L, R, ls(x), l, mid );
if(L > mid) return query(L, R, rs(x), mid+1, r);
return max(query(L, R, ls(x), l, mid), query(L, R, rs(x), mid+1, r));
}
int _main(){
read(n);
for(int i = 1; i <= n; ++i) read(a[i].l, a[i].r);
sort(a+1, a+n+1, [&](Int o1, Int o2){return o1.r<o2.r || (o1.r==o2.r && o1.l<o2.l);});
for(int i = 1; i <= n; ++i)
tmp[++cnt] = a[i].l, tmp[++cnt] = a[i].r;
sort(tmp+1, tmp+cnt+1), cnt = unique(tmp+1, tmp+cnt+1)-tmp-1;
for(int i = 1; i <= n; ++i){
a[i].l = lower_bound(tmp+1, tmp+cnt+1, a[i].l)-tmp;
a[i].r = lower_bound(tmp+1, tmp+cnt+1, a[i].r)-tmp;
}
modify(1, cnt, -1);
for(int i = 1, j = 1; i <= cnt; ++i){
if(i != 1) modify(1, i-1, tmp[i-1]-tmp[i]);
while(j <= n && a[j].r == i)
modify(1, a[j++].l, 1);
if(query(1, i) > 0){
puts("No");
return 0;
}
}
puts("Yes");
return 0;
}
void _init(){
for(int i = 1; i <= (cnt<<2); ++i) val[i] = tag[i] = 0;
cnt = 0;
}
int T;
int main(){
read(T);
while(T--) _init(), _main();
return 0;
}
例题 2(Atcoder arc076_d):给定 \(n\) 个左部点和 \(m\) 个右部点,每个左部点连接边向右部点 \([1, l]\cup[r, m]\)。求最大匹配。
运用 Hall 定理推论。和上题类似的处理方式,改变一下线段树维护的方式即可。
据说可以用 flow 做?
#include <bits/stdc++.h>
#define llong long long
#define N 200005
using namespace std;
#define bs (1<<20)
char buf[bs], *p1, *p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,bs,stdin),p1==p2)?EOF:*p1++)
template<typename T>
inline void read(T& x){
x = 0; char ch = gc();
while(ch < '0' || ch > '9') ch = gc();
while(ch >= '0' && ch <= '9')
x = (x<<3)+(x<<1)+(ch^48), ch = gc();
}
template<typename T, typename... Args>
inline void read(T& x, Args&... y){
return read(x), read(y...);
}
int n, m, ans;
struct Node{
int l, r;
};
Node a[N];
int val[N<<2], tag[N<<2];
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define mid ((l+r)>>1)
inline void build(int x = 1, int l = 1, int r = m+1){
if(l == r) return val[x] = -m+l-1, void();
build(ls(x), l, mid), build(rs(x), mid+1, r);
val[x] = max(val[ls(x)], val[rs(x)]);
return;
}
inline void addtag(int x, int k){
val[x] += k, tag[x] += k;
return;
}
inline void pushdown(int x){
if(!tag[x]) return;
addtag(ls(x), tag[x]);
addtag(rs(x), tag[x]);
tag[x] = 0;
return;
}
inline void modify(int L, int R, int k, int x = 1, int l = 1, int r = m+1){
if(L <= l && R >= r) return addtag(x, k);
pushdown(x);
if(L <= mid) modify(L, R, k, ls(x), l, mid );
if(R > mid) modify(L, R, k, rs(x), mid+1, r);
val[x] = max(val[ls(x)], val[rs(x)]);
return;
}
inline int query(int L, int R, int x = 1, int l = 1, int r = m+1){
if(L <= l && R >= r) return val[x];
pushdown(x);
if(R <= mid) return query(L, R, ls(x), l, mid );
if(L > mid) return query(L, R, rs(x), mid+1, r);
return max(query(L, R, ls(x), l, mid), query(L, R, rs(x), mid+1, r));
}
int main(){
read(n, m), ans = max(ans, n-m);
for(int i = 1; i <= n; ++i) read(a[i].l, a[i].r);
sort(a+1, a+n+1, [&](Node o1, Node o2){return o1.l < o2.l;});
build();
for(int i = 0, j = 1; i <= m; ++i){
while(j <= n && a[j].l== i)
modify(1, a[j++].r, 1);
ans = max(ans, query(i+1, m+1)-i);
}
printf("%d", ans);
return 0;
}

浙公网安备 33010602011771号