[loj6062]「2017 山东一轮集训 Day2」Pair——Hall定理+线段树

14 篇文章 0 订阅
4 篇文章 0 订阅

题目大意:

给出一个长度为 n n 的数列{ai}和一个长度为 m m 的数列{bi} {ai} { a i } 有多少个长度为 m m 的连续子数列能与{bi}匹配。
两个数列可以匹配,当且仅当存在一种方案,使两个数列中的数可以两两配对,两个数可以配对当且仅当它们的和不小于 h h

思路:

感觉还是很好的一道题目。我是为了学Hall定理才找到这道题去做的。
首先我们来考虑如何判断两个串是否可以形成完美匹配,根据Hall定理可以得到,只有a的每一个子集和 b b 相连的点数都大于这个子集本身的点数才具有完美匹配。显然枚举每一个子集是不现实的,但是我们发现b中和 a a 相邻的点只取决于a中权值最大的点,设以 ai a i 为最大权值的子集为 Sai S a i .,我们只需要判断 size(S)bsize(Sai)max s i z e ( S ) b − s i z e ( S a i ) max 是否大于等于0即可。
但是这样还是不够好,把 b b 从小到大排序之后,每一个ai对应的 b b 就是一段后缀,相当于覆盖了一条线段,满足上面的条件其实就是每一个bi被覆盖的次数满足大于等于 i i <script type="math/tex" id="MathJax-Element-1716">i</script>,这样处理之后就很好用线段树来维护了。

/*===============================
 * Author : ylsoi
 * Problem : loj6062
 * Algorithm : Hall&Segment_Tree
 * Time : 2018.6.29
 * ===========================*/
#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a;i<=b;++i)
typedef long long ll;

using namespace std;

void File(){
    freopen("loj6062.in","r",stdin);
    freopen("loj6062.out","w",stdout);
}

const int maxn=1.5e5+10;
int n,m,h,a[maxn],b[maxn],ans;

struct Segment_Tree{
#define mid ((l+r)>>1)
#define lc rt<<1
#define rc rt<<1|1
#define lson lc,l,mid
#define rson rc,mid+1,r
    int Min[maxn<<2],tag[maxn<<2];
    void pushdown(int rt){
        Min[lc]+=tag[rt]; tag[lc]+=tag[rt];
        Min[rc]+=tag[rt]; tag[rc]+=tag[rt];
        tag[rt]=0;
    }
    void build(int rt,int l,int r){
        if(l==r)Min[rt]=-l;
        else{
            build(lson); build(rson);
            Min[rt]=min(Min[lc],Min[rc]);
        }
    }
    void modify(int rt,int l,int r,int L,int R,int x){
        if(L>R)return;
        if(L<=l && r<=R){Min[rt]+=x; tag[rt]+=x;}
        else{
            if(tag[rt])pushdown(rt);
            if(L<=mid)modify(lson,L,R,x);
            if(R>=mid+1)modify(rson,L,R,x);
            Min[rt]=min(Min[lc],Min[rc]);
        }
    }
}T;

void init(){
    scanf("%d%d%d",&n,&m,&h);
    REP(i,1,m)scanf("%d",&b[i]);
    sort(b+1,b+m+1);
    REP(i,1,n){
        scanf("%d",&a[i]);
        a[i]=lower_bound(b+1,b+m+1,h-a[i])-b;
    }
    T.build(1,1,m);
    REP(i,1,m-1)T.modify(1,1,m,a[i],m,1);
}

int main(){
    File();
    init();
    REP(i,m,n){
        T.modify(1,1,m,a[i],m,1);
        ans+=(T.Min[1]>=0);
        T.modify(1,1,m,a[i-m+1],m,-1);
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值