霍尔定理是判断二分图是否是完美匹配的充要条件
首先要求 ∣ X ∣ = ∣ Y ∣ |X|=|Y| ∣X∣=∣Y∣,即是左右两部分的节点个数相等
其次 对于 X X X的任意子集a都要满足 ∣ a ∣ < = ∣ b ∣ |a|<=|b| ∣a∣<=∣b∣,b是a节点所能到达的Y中的节点的并
6062. 「2017 山东一轮集训 Day2」Pair
主要就是因为这个题才了解的霍尔定理
题目:给出一个长度为 的数列 和一个长度为 的数列 ,求 有多少个长度为 的连续子数列能与 匹配。
两个数列可以匹配,当且仅当存在一种方案,使两个数列中的数可以两两配对,两个数可以配对当且仅当它们的和不小于 。
思路:
很明显的一个实事这个匹配与b数组的顺序无关,因此我们可以把b数组从小到大排序,对于一个a[i]来说,b中可以满足条件的数一定都是一个连续的b的后缀,也可以不存在。
c[i]:表示b[i]与a中相匹配的数量,显然如果b[1]可以匹配的数,那么b[1]之后的数也必然匹配,故c[i]是不下降。
如果d[1]可以匹配,那么c[1]>=1,如果d[2],d[1],可以匹配,那么c[1]>=1,c[2]>=2.其中的c[2]的一个1是d[1]贡献的,所以如果m个连续的a可以完美匹配,那么满足c[i]>=i;
因此判断一个连续的a是否完美匹配,可以先计算出c[i],然后判断每个c[i]是否都满足条件。这里有一个很nice的小技巧。提前将每一个c位置先减去i,最后找出c中的最小值,如果最小值>=0,说明可以。
#include
using namespace std;
const int N = 2 * 1000 * 100;
int a[N], b[N];
int n, m, h;
int val[N<<2],la[N<<2];
int getid(int x) {
return lower_bound(b+1,b+1+m,x)-b;
}
void down(int rt) {
val[rt] = min(val[rt<<1],val[rt<<1|1]);
}
void built(int rt,int L,int R) {
la[rt] = 0;
if (L == R) {
val[rt] = -L; return;
}
int mid = L + R >> 1;
built(rt<<1,L,mid);
built(rt<<1|1,mid+1,R);
down(rt);
}
void pushdown(int rt) {
if (la[rt]) {
int k = la[rt]; la[rt] = 0;
la[rt << 1] += k, la[rt << 1 | 1] += k;
val[rt << 1] += k, val[rt << 1 | 1] += k;
}
}
void change(int rt, int l, int r,int L,int R,int k) {
if (L > R)return;
if (l >= L && r <= R) {
la[rt] += k; val[rt] += k;
return;
}
pushdown(rt);
int mid = l + r >> 1;
if(L<=mid)change(rt<<1,l,mid,L,R,k);
if(R>mid)change(rt<<1|1,mid+1,r,L,R,k);
down(rt);
}
int main() {
scanf("%d%d%d",&n,&m,&h);
for (int i = 1; i <= m; i++)scanf("%d",b+i);
for (int i = 1; i <= n; i++)scanf("%d",a+i);
int ans = 0;
sort(b+1,b+1+m);
built(1,1,m);
for (int i = 1; i <= m; i++) {
change(1, 1, m, getid(h - a[i]), m, 1);
}
if(val[1]>=0)ans++;
for (int i = m + 1; i <= n; i++) {
change(1, 1, m, getid(h - a[i - m]),m ,-1);
change(1, 1, m, getid(h - a[i]),m ,1);
if (val[1] >= 0)ans++;
}
printf("%d",ans);
return 0;
}