UNV637 【美团杯2021 A】数据结构

题意简介

给定一个长度为 n 的序列 a,序列的标号从 1 开始,每一个位置是一个 [1,n] 内的整数。有 m 个操作,每次操作给出一个区间 [l,r]:这次操作会先将下标在
[l,r] 中的所有元素加上 1,然后询问全局颜色数,并在最后撤销这次修改(也就是说操作之间互不影响)。

做法分析

问的是颜色数,也即考虑每一种数值出现与否。考虑每种数什么时候出现或不出现。一个数 x 在一次询问做出贡献,要么原序列最左与最右的 x 的下标构成的 l,r 被询问区间包含,要么原序列存在值为 x1 的元素下标被询问区间包含。上述条件反过来就是做不出贡献的情况。

由于这是CNDS考虑把询问离线下来,贡献加减变成矩形做扫描线。具体来说,这个扫描线长这个样子。我们记 tot 为所有可达的数字(也就是说,那些无法在任何询问中出现的数字我们忽略不记),把每个数字做不出贡献的 l,r 范围框定出来。

具体怎么框定?对于只能由 x1 加上来的数,其做不出贡献的 l,r 对形如一个个 l[xi,xi+t],r[xi,xi+t]。(记为一类)(也即下图中 3,6,8 的样子)

除此之外,其做不出贡献的区间形如 l(ll,rl],r[lr,rr),其中 lr,rl 分别为序列中最小和最大的的值为 x 的下标,ll,rr 分别为 lr 左侧最靠近 lr 的和 rl 右侧最靠近 rl 的,值为 x1 的下标。(记为二类)

特殊地,当存在 x1 位于两个 x 之间时,x 永远不会消失,因此特判掉,其不会带来任何扫描线。
(为什么这里维护做不出贡献的区间?因为区间数量少,空间复杂度有保证。)

举个例子:当序列为 21247574 时:
pEAUF0A.png

(废话?预警)哦那么扫描线有多少条呢?首先一个矩形对应两条线,所以空间自带两倍常熟。二类贡献显然是每个 O(1) 的。一类呢?观察到对于这样的每个 xx1 出现 k 次时,会有 k+1 个正方形(不一定取满)。粗略地看,若总共有 y 个这样的 x,每个 x 出现 ki 次,可以认为空间就会占 i=1yki+1=ki+y。又 ki 可以认为是个定值,所以 y 越多时空间占的就越多(说大白话就是每种 x1 只出现一次,每个正方形就出现两次,这个过程还要重复 y 次,差不多等于说是两倍空间了)。

所以简简简而言之就是:存线段数组开四倍空间。

代码实现

找那啥二类的 ll,rr 等等时,为防止 ll,rr 不存在,往 apr 里面塞 0n+1
如果 tmp1 找到了 N+1要特殊处理。

#include <bits/stdc++.h>
using namespace std;
namespace obasic{
    typedef vector<int> vecint;
    template <typename _T>
    void frdi(_T &x){
        _T k=1;x=0;char ch=getchar();
        for(;!isdigit(ch);ch=getchar())if(ch=='-')k=-1;
        for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+ch-'0';
        x*=k;return;
    }
    template <typename _T>
    void fwri(_T x){
        if(x<0)putchar('-'),x=-x;
        if(x>9)fwri(x/10);
        putchar(x%10+'0');
    }
    template <typename _T>
    int lwberv(vector<_T> &vec,_T val){return *lower_bound(vec.begin(),vec.end(),val);}
};
using namespace obasic;
const int MaxN=1e6+5;
int N,M,A[MaxN],pre[MaxN],pap[MaxN];
int acnt[MaxN],tot,ans[MaxN];
struct quer{int x,y,id;}Q[MaxN];
bool cmp1(quer a,quer b){return a.x<b.x;}
vecint apr[MaxN];int scnt;
struct aseg{int x,ly,ry,v;}S[MaxN<<2];
void addrect(int lx,int rx,int ly,int ry){
    S[++scnt]={lx,ly,ry,1};
    S[++scnt]={rx+1,ly,ry,-1};
}
bool cmp2(aseg a,aseg b){return a.x<b.x;}
struct BinidTree{
    int n,t[MaxN];
    void init(int x){n=x,memset(t,0,sizeof(t));}
    int lowbit(int x){return x&(-x);}
    void add(int p,int x){for(;p<=n;p+=lowbit(p))t[p]+=x;}
    void update(int l,int r,int x){add(l,x),add(r+1,-x);}
    int gts(int p){int res=0;for(;p;p-=lowbit(p)){res+=t[p];}return res;}
}BidTr;
int main(){
    frdi(N),frdi(M);BidTr.init(N+1),tot=N+1;
    for(int i=0;i<=N+1;i++)apr[i].push_back(0);
    for(int i=1;i<=N;i++){
        frdi(A[i]);apr[A[i]].push_back(i);
        acnt[A[i]]++;pre[i]=pap[A[i]],pap[A[i]]=i;
    }
    for(int i=0;i<=N+1;i++)apr[i].push_back(N+1);
    int tmp1,tmp2,clx,crx,cly,cry;
    for(int i=1;i<=N+1;i++){
        int cc=acnt[i],pc=acnt[i-1];
        if(!cc){
            if(!pc){tot--;continue;}
            for(int j=1,p,q=0;j<=pc+1;j++){
                p=q,q=apr[i-1][j];
                if(q==p+1)continue;
                addrect(p+1,q-1,p+1,q-1);
            }
            continue;
        }
        tmp1=lwberv(apr[i-1],apr[i][1]);
        if(tmp1<apr[i][acnt[i]])continue;
        tmp2=lwberv(apr[i-1],apr[i][cc]);
        crx=apr[i][1],cly=apr[i][acnt[i]];
        clx=(tmp1==N+1?apr[i-1][pc]:pre[tmp1])+1,cry=tmp2-1;
        addrect(clx,crx,cly,cry);
        continue;
    }
    sort(S+1,S+scnt+1,cmp2);
    for(int i=1;i<=M;i++)frdi(Q[i].x),frdi(Q[i].y),Q[i].id=i;
    sort(Q+1,Q+M+1,cmp1);for(int i=1,p=1,q=1;i<=N+1;i++){
        for(;p<=scnt&&S[p].x==i;p++)BidTr.update(S[p].ly,S[p].ry,S[p].v);
        for(;q<=M&&Q[q].x==i;q++)ans[Q[q].id]=tot-BidTr.gts(Q[q].y);
    }
    for(int i=1;i<=M;i++)fwri(ans[i]),puts("");
    return 0;
}
posted @   矞龙OrinLoong  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示