• @loj



    @description@

    给出一个长度为 n 的数列 {ai} 与一个长度为 m 的数列 {bi},求 {ai} 有多少个长度为 m 的连续子数列与 {bi} 匹配。

    两个数列可以匹配,当且仅当存在一种方案,使两个数列中的数可以两两配对。

    两个数可以配对,当且仅当它们的和不小于 h。

    输入格式
    第一行三个整数 n, m, h。
    第二行有 m 个数字 b1, b2, ..., bm。
    第三行有 n 个数字 a1, a2, ..., an。

    输出格式
    输出一个数字,{ai} 有多少个长度为 m 的连续子数列能与 {bi} 匹配。

    样例
    样例输入 1
    5 2 10
    5 3
    1 8 5 5 7
    样例输出 1
    2

    数据范围与提示
    对于 100% 的数据,1 <= m <= n <= 150000;1 <= ai, bi, h <= 10^9。

    @solution@

    这里的匹配其实就是二分图的匹配。
    然而这个数据范围,如果真的写二分图匹配,怕是连边都建不出来。
    但这道题只需要询问二分图最大匹配的存在性,而我们有 hall 定理描述了二分图最大匹配存在性。

    hall 定理表明,从二分图左边任选一个点集 S,取出与 S 中的点有边相连的右边的点,构成邻集 T。若对于任意一个 S 都满足 |S| <= |T|,则二分图最大匹配存在。
    我们一般用的时候,需要尝试去找一个不满足 hall 定理的点集,即 |S| > |T| 的点集 S,或者等价地记作 |S| - |T| > 0。

    注意这道题有个性质:如果 ai >= aj,则 aj 所连的 bk,ai 也能够连向 bk。这个比较显然,因为 ai + bk >= aj + bk >= h。
    于是我们设点集 S 中权值最大的为 ax,显然 S 对应的 T 是由 ax 的值唯一决定的。于是如果要让 |S| - |T| > 0,就是尽可能地要让 S 大,可以发现最大是把所有 <= ax 的全部塞进 S 里面。

    先处理出使 ai + bj >= h 的 bj 数量,记作 f[i]。
    对于一个二分图,我们有 O(n) 的检验方法:对于每一个 i,统计这个二分图内 aj <= ai 的 j 的个数 p,判断是否 p - f[i] > 0,如果是则无法匹配。
    注意到每次二分图的变化只会多一个点 + 少一个点,所以我们可以对权值建线段树,快速维护出 p - f[i] 的最大值(代码中所呈现的是 f[i] - p 的最小值)。

    @accepted code@

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    const int MAXN = 150000;
    int d[MAXN + 5], dcnt = 0;
    int a[MAXN + 5], b[MAXN + 5];
    int n, m, h;
    struct segtree{
    	#define lch x<<1
    	#define rch x<<1|1
    	struct node{
    		int l, r;
    		int tg, mn;
    	}t[4*MAXN + 5];
    	void pushup(int x) {t[x].mn = min(t[lch].mn, t[rch].mn);}
    	void pushdown(int x) {
    		if( t[x].tg ) {
    			t[lch].tg += t[x].tg, t[lch].mn += t[x].tg;
    			t[rch].tg += t[x].tg, t[rch].mn += t[x].tg;
    			t[x].tg = 0;
    		}
    	}
    	void build(int x, int l, int r) {
    		t[x].l = l, t[x].r = r, t[x].tg = t[x].mn = 0;
    		if( l == r ) return ;
    		int mid = (l + r) >> 1;
    		build(lch, l, mid), build(rch, mid + 1, r);
    	}
    	void modify(int x, int l, int r, int d) {
    		if( l > t[x].r || r < t[x].l )
    			return ;
    		if( l <= t[x].l && t[x].r <= r ) {
    			t[x].mn += d, t[x].tg += d;
    			return ;
    		}
    		pushdown(x);
    		modify(x << 1, l, r, d);
    		modify(x << 1 | 1, l, r, d);
    		pushup(x);
    	}
    }T;
    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]), d[++dcnt] = a[i];
    	sort(d + 1, d + dcnt + 1);
    	dcnt = unique(d + 1, d + dcnt + 1) - d - 1;
    	T.build(1, 1, dcnt);
    	for(int i=1;i<=m;i++)
    		T.modify(1, lower_bound(d + 1, d + dcnt + 1, h - b[i]) - d, dcnt, 1);
    	for(int i=1;i<=n;i++)
    		a[i] = lower_bound(d + 1, d + dcnt + 1, a[i]) - d;
    	for(int i=1;i<=m;i++) T.modify(1, a[i], dcnt, -1);
    	int ans = (T.t[1].mn >= 0);
    	for(int i=m+1;i<=n;i++) {
    		T.modify(1, a[i], dcnt, -1), T.modify(1, a[i-m], dcnt, 1);
    		ans += (T.t[1].mn >= 0);
    	}
    	printf("%d
    ", ans);
    }
    

    @details@

    代码中处理 f[i] 的时候是直接在线段树中加加减减。

    其他的。。。应该就没有什么值得注意的细节了吧。

  • 相关阅读:
    埃及分数问题(JLNUOJ 2372)
    哈夫曼树的简单实现(JLOJ2370)
    4 Values whose Sum is 0 UVA 1152
    UVA 1600 Patrol Robot
    UVA122 二叉树的层次遍历
    51nod(1174 区间中最大的数)(ST表模板题)
    51nod(1089 最长回文子串 V2)(hash 加二分)
    中国剩余定理(费马小定理求逆元)
    hdu1005 矩阵快速幂
    八连通(vector动态数组法)
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11410820.html
Copyright © 2020-2023  润新知