[二分图] [线段树] LOJ6062 Pair

posted on 2024-07-16 08:35:41 | under | source

首先对 \(b\) 从小到大排序。

考虑一次查询怎么做,显然 \(a\) 可以匹配的 \(b\) 构成一段后缀,但是还是不太好搞,因为 \(a\) 不能随意定序。

所以把目光放到 \(b\) 上,建出一张二分图,对 \(b\) 使用 \(\rm Hall\) 定理。设 \(S_i\) 表示 \(b_i\) 相连的,那么后缀的性质等价于:对于 \(i<j\),有 \(S_i\in S_j\)

所以贪心的想,假如选择了 \(b_r\),那么就可以随意选择 \(b_{1}\dots b_{r-1}\),因为这并不改变相连的 \(a\) 集合的大小。

于是我们枚举前缀 \(b_1\dots b_r\),那么必须满足 \(\forall r,r\le |S_r|\),即 \(|S_r|-r\ge 0\)

对于区间移动,用线段树维护这个东西的最小值就好了。

复杂度 \(O(n\log n)\)

代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int N = 2e5 + 5;
int n, m, h, x, b[N], p[N], ans;

namespace Sg_Tree{
	#define lt (u << 1)
	#define rt (u << 1 | 1) 
	#define mid (l + r >> 1)
	int t[N << 2], tag[N << 2];
	
	inline void psup(int u) {t[u] = min(t[lt], t[rt]);}
	inline void psdw(int u, int l, int r){
		t[lt] += tag[u], t[rt] += tag[u];
		tag[lt] += tag[u], tag[rt] += tag[u];
		tag[u] = 0;
	}
	inline void build(int u, int l, int r){
		if(l == r) {t[u] = -l; return ;}
		build(lt, l, mid), build(rt, mid + 1, r);
		psup(u);
	}
	inline void upd(int u, int l, int r, int ll, int rr, int p){
		if(ll > rr) return ;
		if(ll <= l && r <= rr) {t[u] += p, tag[u] += p; return ;}
		psdw(u, l, r);
		if(ll <= mid) upd(lt, l, mid, ll, rr, p);
		if(rr > mid) upd(rt, mid + 1, r, ll, rr, p);
		psup(u);
	}
}using namespace Sg_Tree;
signed main(){
	cin >> n >> m >> h;
	for(int i = 1; i <= m; ++i) scanf("%lld", &b[i]);
	sort(b + 1, b + 1 + m), build(1, 1, m);
	for(int i = 1; i <= n; ++i){
		scanf("%lld", &x);
		int L = 0, R = m + 1, Mid;
		while(L + 1 < R){
			Mid = L + R >> 1;
			if(b[Mid] + x >= h) R = Mid;
			else L = Mid;
		}
		p[i] = R;
//		cout << p[i] << ' ' << m << endl;
	}
	for(int i = 1; i <= m; ++i) upd(1, 1, m, p[i], m, 1);
	for(int i = 1; i + m - 1 <= n; ++i){
		if(t[1] >= 0) ++ans;
		if(i + h - 1 == n) break;
		upd(1, 1, m, p[i], m, -1);
		upd(1, 1, m, p[i + m], m, 1);
	}
	cout << ans;
	return 0;
} 
posted @ 2026-01-12 20:13  Zwi  阅读(1)  评论(0)    收藏  举报