前置知识:霍尔定理(Hall’s Theorem) #
也叫霍尔结婚定理 (Hall’s marriage theorem)。在二分图中,令两部分点集分别为, 则存在完美匹配(中的点集全部被匹配)的充分必要条件是:对于的任意子集,,其中为与直接相连的点的集合。
题解 #
本题是让我们求最大匹配数,但直接跑匹配算法肯定不合适,此时我们考虑霍尔定理:假设所有人的集合为,我们至少还需要。但是的子集的个数是指数级的所以不能直接考虑子集。
但是我们发现总是所有椅子的一个前缀加一个后缀,也就是。于是我们可以考虑枚举,那么椅子的集合所对应的人的集合为,此时。但很显然的个数是的,还是太慢,不过这已经是一个很大的进步了。
我们再想进一步优化,从小到大枚举,通过某些数据结构直接求得所有中的最大值:我们可以用线段树维护对于每一个,的最大值。对于每一个人的限制条件,当我们枚举到时,如果选择的的话,那么对应的人的集合就会包含,反映到维护的值上去的话就是把区间里的值 +1。然后用中的最大值减当前的来更新答案。
注意几点:
- 按 l 从小到大的顺序可以保证前面的都是符合条件的,只要考虑的取值即可。
- 维护的值是把除去的,因为我们只考虑 的取值。
- 每个位置的都是固定的,所以建树的时候就可以加进去。
- 特殊情况当为整个椅子的集合时,
代码 #
#include <bits/stdc++.h>
using namespace std;
template <typename T> struct SegTree {
int n, M;
vector<T> t;
SegTree(int n_, int _m) : n(n_), M(_m), t(4 * n) {
build(1, 0, n - 1);
}
void pull(int node) { t[node] = t[node * 2] + t[node * 2 + 1]; }
void build(int node, int l, int r) {
if (l == r) { return t[node].apply(l, r, -M + r - 1); }
int mid = (l + r) / 2;
build(node * 2, l, mid);
build(node * 2 + 1, mid + 1, r);
pull(node);
}
void push(int p, int l, int r) {
if (t[p].lazy) {
int m = (l + r) / 2;
t[p * 2].apply(l, m, t[p].lazy);
t[p * 2 + 1].apply(m + 1, r, t[p].lazy);
t[p].lazy = 0;
}
}
void add(int node, int ql, int qr, int l, int r, int x) {
if (r < ql || l > qr) return;
if (ql <= l && qr >= r) return t[node].apply(l, r, x);
push(node, l, r);
int mid = (l + r) / 2;
add(node * 2, ql, qr, l, mid, x);
add(node * 2 + 1, ql, qr, mid + 1, r, x);
pull(node);
}
T get(int node, int ql, int qr, int l, int r) {
if (ql <= l && qr >= r) return t[node];
push(node, l, r);
int mid = (l + r) / 2;
if (qr <= mid) return get(node << 1, ql, qr, l, mid);
if (ql > mid) return get(node << 1 | 1, ql, qr, mid + 1, r);
return get(node << 1, ql, qr, l, mid) +
get(node << 1 | 1, ql, qr, mid + 1, r);
}
// wrapper
void add(int l, int r, int x) {
assert(l >= 0 && l <= r && r < n);
add(1, l, r, 0, n - 1, x);
}
T get(int l, int r) {
assert(l >= 0 && l <= r && r < n);
return get(1, l, r, 0, n - 1);
}
};
struct node {
int v = 0; // don't forget to set default value (used for leaves),
// not necessarily zero element
int lazy = 0;
void apply(int l, int r, int x) {
lazy += x;
v += x;
}
node operator+(const node &b) const {
node res;
res.v = max(v, b.v);
return res;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
const int M = 200000;
vector<vector<int>> rs(M + 1);
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
rs[l].push_back(r);
}
SegTree<node> tr(m + 2, m);
int ans = n - m;
for (int l = 0; l <= M && l <= m - 1; l++) {
for (auto r : rs[l]) {
tr.add(l + 1, r, 1);
}
ans = max(ans, tr.get(l + 1, m + 1).v - l);
}
cout << ans << '\n';
}