【LOJ6062】「2017 山东一轮集训 Day2」Pair(线段树套路题)

本文介绍了一种利用线段树进行优化的匹配算法,用于解决数列匹配问题。通过预处理和离散化,结合线段树实现高效判断两个数列是否匹配,即是否存在一种方案使两个数列中的数两两配对,配对数的和不小于给定阈值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点此看题面

大致题意: 给出一个长度为\(n\)的数列\(a\)和一个长度为\(m\)的数列\(b\),求\(a\)有多少个长度为\(m\)的子串与\(b\)匹配。数列匹配指存在一种方案使两个数列中的数两两配对,数配对指它们的和不小于\(h\)

预处理

显然,要判断两个数列是否匹配,肯定是将一个数列从小到大排序,另一个数列从大到小排序,然后逐一判断相应位置上的两个数是否配对。

我们可以将其转化,把\(b_i\)变成\(h-b_i\),然后将\(a\)的某个子串和\(b\)都从大到小排序,再比较相应位置上\(a_i\)是否大于等于\(b_i\),即可判断是否匹配。

线段树优化

然后就可以发现这是一道线段树套路题。

考虑要对应位置\(a_i\)大于等于\(b_i\),且\(b_1\le b_2\le b_3\le...\le b_n\),则对于\(b_i\),应该有\(\ge i\)个数大于等于它。

所以,我们可以将\(a,b\)离散化,对于每个\(b_i\),在线段树第\(b_i\)个位置上减\(i\),对于每个\(a_i\),在线段树第\([1,a_i]\)这个区间上加\(1\)

因此,判断线段树中是否所有元素大于等于\(0\)即可判断两个序列是否匹配(这只须记录一个最小值就能判)。

要注意的是,对于重复的\(b_i\),我们只选择最大的\(i\)减去,而不是叠加。

既然这样,我们枚举\(a\)中子串右端点\(i\),每次删去第\(i-m\)个数,然后加上第\(i\)个数,若当前情况合法则将\(ans\)\(1\)即可。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 150000
#define min(x,y) ((x)<(y)?(x):(y))
using namespace std;
int n,m,h,dc,a[N+5],b[N+5],dv[N+5];
class FastIO
{
    private:
        #define FS 100000
        #define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
        #define tn (x<<3)+(x<<1)
        #define D isdigit(c=tc())
        char c,*A,*B,FI[FS];
    public:
        I FastIO() {A=B=FI;}
        Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
        Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
class SegmentTree//线段树
{
    private:
        #define L l,mid,rt<<1,tl,tr
        #define R mid+1,r,rt<<1|1,tl,tr
        #define PU(x) (O[x]=O[x<<1]+O[x<<1|1])
        #define PD(x) (O[x].F&&(O[x<<1]+=O[x].F,O[x<<1|1]+=O[x].F,O[x].F=0))
        struct node//存储节点信息
        {
            int Mn,F;I node(CI mn=0,CI f=0):Mn(mn),F(f){}
            I node operator + (Con node& o) Con {return node(min(Mn,o.Mn));}
            I void operator += (CI x) {Mn+=x,F+=x;}
        }O[N<<2];
        I void upt(CI l,CI r,CI rt,CI tl,CI tr,CI v)//区间修改
        {
            if(tl<=l&&r<=tr) return O[rt]+=v;PD(rt);RI mid=l+r>>1;
            tl<=mid&&(upt(L,v),0),tr>mid&&(upt(R,v),0),PU(rt);
        }
    public:
        I void Update(CI l,CI r,CI v) {upt(1,n,1,l,r,v);}
        I bool Check() {return O[1].Mn>=0;}//判断最小值是否大于等于0
}S;
I int GV(CI x)//求出离散化后的值
{
    RI l=1,r=dc,mid;W(l<=r) dv[mid=l+r>>1]<x?l=mid+1:r=mid-1;
    return l;
}
int main()
{
    RI i,ans=0;for(F.read(n,m,h),i=1;i<=m;++i) F.read(b[i]);//读入
    for(sort(b+1,b+m+1),i=1;i<=n;++i) F.read(a[i]),dv[i]=a[i];
    sort(dv+1,dv+n+1),dc=unique(dv+1,dv+n+1)-dv-1;for(i=1;i<=n;++i) a[i]=GV(a[i]);//离散化
    for(i=1;i<=m;++i) b[i]=GV(h-b[i]),b[i]<h&&(S.Update(b[i],b[i],b[i]^b[i-1]?-i:-1),0);//离散化,并在线段树上初始化,注意判重
    for(i=1;i<=m;++i) S.Update(1,a[i],1);ans+=S.Check();//将前m个数加入,更新ans
    for(i=m+1;i<=n;++i) S.Update(1,a[i-m],-1),S.Update(1,a[i],1),ans+=S.Check();//每次删除第i-m个数,加上第i个数,然后更新ans
    return printf("%d",ans),0;//输出答案
}

转载于:https://www.cnblogs.com/chenxiaoran666/p/LOJ6062.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值