霍尔定理

霍尔定理是判断二分图是否是完美匹配的充要条件

首先要求 ∣ 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;
}

你可能感兴趣的:(霍尔定理,二分匹配)