NOIP提高组【JZOJ4804】成绩调研

14 篇文章 0 订阅
2 篇文章 0 订阅

Description

这里写图片描述

Data Constraint

这里写图片描述

Solution

这道题我们考虑用线段树和单调队列解决。显然我们枚举右边界j,那么左边界的所在的范围显然是一个形如[l..r]的区间。

对于区间的左端点,我们设一个左指针i,i表示[i..j]范围内所有的等级均小于等级数量的最大限制,我们再弄一个数组f,f[i]表示当前i等级的数量,若当前f[a[j]]大于最大限制,我们就将左指针往右移。这样,我们就可以处理出答案区间的左端点了。

对于区间的右端点,我们用线段树来处理。我们用线段树来处理当前的所有等级数量大于等于最小限制x时,该等级的从后往前数第x个位置的最小值。因为这样我们就可以保证在这个最小值之后所有等级的数量均大于自身的最小数量限制。

最后对于每一个j,若当前满足大于等级数量最小限制的登等级数量等于m,就统计答案。

代码

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=200005;
int f[maxn],l[maxn],a[maxn],r[maxn],next[maxn],d[maxn],g[maxn*10],g1[maxn];
int n,i,t,j,k,m,num,z,mx;
ll ans,x,y;
bool bz[maxn];
void change(int l,int r,int v){
    int mid=(l+r)/2;
    if (l==r){
        if (y<0) g[v]=mx; 
        else if (g[v]!=mx)g[v]=d[g[v]];
        else g[v]=y;return;
    }
    if (mid>=x) change(l,mid,v*2);
    else change(mid+1,r,v*2+1);
    g[v]=min(g[v*2+1],g[v*2]);
}
int main(){
    freopen("survey.in","r",stdin);freopen("survey.out","w",stdout);
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;i++)
        scanf("%d",&a[i]);
    memset(g,127,sizeof(g));mx=g[1];
    for (i=1;i<=m;i++)
        next[i]=n+1;
    for (i=n;i>=1;i--) d[i]=next[a[i]],next[a[i]]=i;
    for (i=1;i<=m;i++){
        scanf("%d%d",&l[i],&r[i]);
        if (!l[i]) bz[i]=true,num++;
    }
    i=1;
    for (j=1;j<=n;j++){
        f[a[j]]++;
        if (!g1[a[j]]) g1[a[j]]=j;
        if (f[a[j]]>=l[a[j]] && !bz[a[j]]) {
            num++,bz[a[j]]=true,x=a[j],y=g1[a[j]],change(1,m,1);
        }
        if (f[a[j]]>l[a[j]] && l[a[j]]){
            x=a[j],y=0,change(1,m,1);g1[a[j]]=d[g1[a[j]]];
        }
        while (f[a[j]]>r[a[j]]){
            f[a[i]]--;
            if (g1[a[i]]==i) g1[a[i]]=d[i];
            if (f[a[i]]<l[a[i]] && bz[a[i]] && l[a[i]])y=-1,x=a[i],change(1,m,1),num--,bz[a[i]]=false;
            i++;
        }
        if (num==m){
            x=g[1],y=i,ans+=x-y+1;
        }
    }
    printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值
>