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;
}

posted @ 2026-05-17 14:14  Hootime  阅读(14)  评论(0)    收藏  举报