NOIP提高组【JZOJ4804】成绩调研

来源:互联网 发布:淘宝连接类目怎么修改 编辑:程序博客网 时间:2024/06/11 13:39

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 longusing 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);}
3 0