静态莫队

莫队,对询问离线并进行合理的排序,处理完一个区间的询问后,可以以常数的时间复杂度转移到下一个询问的答案,通常初始化l=1,r=0,奇偶化排序

离线询问结构体

struct ask{
    int l,r,t,ans;/*如果每个询问不需要拆成多个,那么可以直接在这里记录答案*/
    inline bool friend operator<(const ask&a,const ask&b){
        return bl[a.l]!=bl[b.l]?/*分块排序*/a.l<b.l:bl[a.l]&1?/*奇偶化*/a.r<b.r:a.r>b.r;
    }
}q[N];

初始化及莫队主体

signed main(){
    int len=sqrt(n);
    for(int i=1;i<=n;i++)bl[i]=(i-1)/len+1;
    for(int i=1;i<=m;i++)cin>>q[i].l>>c[i].r,q[i].t=i;
    sort(q+1,q+1+m);
    for(int i=1,l=1,r=0;i<=m;i++){
        while(l>q[i].l)add(a[--l]);/*a是维护的序列,特殊情况下也可以只传入l,r*/
        while(r<q[i].r)add(a[++r]);
        while(l<q[i].l)del(a[l++]);
        while(r>q[i].r)del(a[r--]);
        q[q[i].t].ans=ans;
    }
    for(int i=1;i<=m;i++)cout<<q[i].ans<<'\n';
}

询问区间内的数是否互不相同。每个数开一个桶,第一次出现ans++,减少为0ans--

inline void add(int x){if(++cnt[x]==1)re++;}
inline void del(int x){if(--cnt[x]==0)re--;}
        q[q[i].t].ans=re==r-l+1;
	for(int i=1;i<=m;i++)cout<<(q[i].ans?"Yes\n":"No\n");
	return 0;
}

询问区间[l,r]的sum(i=1->k)(c[i]*c[i]),k是值域,c[i]是i在[l,r]内出现的次数。cnt[i]为a[i]出现的次数,注意进出队时队cnt更新的先后顺序不同,使得对答案的更新也不同

inline void add(int x){ans+=((++cnt[x])<<1)-1;}
inline void del(int x){ans-=((--cnt[x])<<1)+1;}

询问sum(x=0->INF)(get(l1,r1,x)get(l2,r2,x)),get(l,r,x)表示x在[l,r]内的出现次数。差分,get(l,r,x)=get(1,r,x)-get(1,l-1,x),原式=sum(x=0->INF)(get(1,r1)get(1,r2)-get(1,r1)get(1,l2-1)-get(1,l1-1)get(1,r2)+get(1,l1-1)*get(1,l2-1)),拆成四个询问,注意此时l,r并不是代表区间端点,而是同时处理两个区间的信息,l,r都要从0开始,l左移,x的cnt1减少1,cnt2不变,对答案贡献-cnt2,右移,x的cnt1增加1,cn2不变,对答案的贡献+cnt2;r左移,x的cnt2减少1,cnt1不变,对答案的贡献-cnt1,右移,x的cnt2增加1,cnt1不变,对答案的贡献+cnt1

    for(int i=1;i<=m;i++){
        int l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        q[i-1<<2]={i,r1,r2,1};
        q[i-1<<2|1]={i,l2-1,r1,-1};
        q[i-1<<2|2]={i,l1-1,r2,-1};
        q[i-1<<2|3]={i,l1-1,l2-1,1};
    }
    sort(q,q+(m<<2));
    for(int i=0,l=0,r=0,re=0;i<(m<<2);i++){
        while(l>q[i].l)re-=cr[a[l]],--cl[a[l--]];
        while(l<q[i].l)re+=cr[a[++l]],++cl[a[l]];
        while(r<q[i].r)re+=cl[a[++r]],++cr[a[r]];
        while(r>q[i].r)re-=cl[a[r]],--cr[a[r--]];
        ans[q[i].t]+=re*q[i].flag;
    }

询问区间内恰好出现两次的数的数量。先离散化,每个数一个桶,第一次加到2是re++,若这次是从2到3则re--,第一次减到2re++,若这次是从2到1则re--

inline void add(int x){re+=(++cnt[x]==2)-(cnt[x]==3);}
inline void del(int x){re+=(--cnt[x]==2)-(cnt[x]==1);}

询问区间内有多少个子区间编号重排后可以成为一个回文串。回文串只可能是所有字母偶数个或者仅有一个字母奇数个,异或前缀和处理,q[i]表示区间[1,i]的各个字母出现的状态,[x,y]的状态为a[x-1]^a[y],选择i属于[x-1,y]的两个不同位置进行疑惑是否能得到0后2的某次方,每次进队相当于多了一个a值x可用,查询原来cnt[x],即为异或为0的个数,在查询有多少与它异或为2的某次方的个数,注意读入时询问的左端点减一

inline void add(int x){
    ans+=cnt[x]++;
    for(int i=0;i<26;i++)ans+=cnt[x^(1<<i)];
}
inline void del(int x){
    ans-=--cnt[x];
    for(int i=0;i<26;i++)ans-=cnt[x^(1<<i)];
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    int len=sqrt(n);
    for(int i=1;i<=n;i++){
        char c;cin>>c;
        a[i]=a[i-1]^(1<<c-'a');
        bl[i]=(i-1)/len+1;
    }
    for(int i=1;i<=m;i++)cin>>q[i].l>>q[i].r,q[i].t=i,q[i].l--;

询问区间中是否存在a,b使得a-b=x或a+b=x或a*b=x,x每次询问不同。用两个bitset维护,对于a-b=x,a=b+x,即s[y]=s[y+x]=true,s&(s<<x)存在1;对于a+b=x,转换为减法a-(-b)=x,但是bitset无法维护负数,所以另一个bitset维护N-x;对于乘法,枚举因数

bitset<N>s,s0;
inline void add(int x){if(++cnt[x]==1)s[x]=s0[N-x]=true;}
inline void del(int x){if(--cnt[x]==0)s[x]=s0[N-x]=false;}
        switch(q[i].op){
            case 1:q[q[i].t].ans=(s&(s<<x)).any();break;
            case 2:q[q[i].t].ans=(s&(s0>>(N-x))).any();break;
            case 3:for(int j=1;j*j<=x;j++)if((!(x%j))&&s[j]&&s[x/j]){q[q[i].t].ans=true;break;}break;
        }

每次询问区间众数的个数。离散化,为每个数统计次数,为每个次数统计个数,入队时ans取max,出队时若ans是当前颜色的次数并且当前次数仅有一个则ans--

inline void add(int x){
    sum[++cnt[a[x]]]++;
    ans=max(ans,cnt[a[x]]);
}
inline void del(int x){
    ans-=(sum[cnt[a[x]]]==1&&ans==cnt[a[x]]);
    sum[cnt[a[x]]--]--;
}

询问区间内第k小的热度,热度为该种类出现的次数。离散化后对值域分块,维护每个数的出现次数,维护每个次数出现的个数。

inline void modify(int x,int v){/*对于热度进行值域分块*/
    num[x]+=v;/*当前热度出现次数进行更新*/
    sz[bl[x]]+=v;/*当前热度所属的块进行更新*/
}
inline void add(int x){/*当前股票种类x*/
    if(cnt[x])/*x出现过*/modify(cnt[x],-1);/*更新x热度所在块*/
    modify(++cnt[x],1);/*当前股票热度+1并更新所在块*/
}
inline void del(int x){
    modify(cnt[x]--,-1);
    if(cnt[x])modify(cnt[x],1);
}
inline int ask(int x){
    for(int i=1;i<=bl[n];i++){
        if(x>sz[i])x-=sz[i];
        else{
            for(int j=st[i];j<=ed[i];j++){
                x-=num[j];
                if(x<=0)return j;
            }
        }
    }
    return -1;
}
        q[q[i].t].ans=ask(q[i].k);

询问区间内最小未出现的自然数。离散化后对值域分块,注意第一个块的左端点是0,当一个数第一次出现时表示vi,并且增加其所属块中不同数的个数;消失时,标记vis并减少所属块中不同数的个数

inline void add(int x){
    if(++cnt[x]==1)sum[bl[x]]+=(vis[x]=1);
}
inline void del(int x){
    if(!--cnt[x])sum[bl[x]]-=(vis[x]=0)^1;
}
inline int ask(){
    int p=1;
    while(sum[p]==ed[p]-st[p]+1)p++;
    for(int i=st[p];i<=ed[p];i++)if(!vis[i])return i;
    return -1;
}
    int len=sqrt(N-4);
    for(int i=0;i<=N-4;i++)bl[i]=1+i/len;
    for(int i=1;i<=bl[N-4];i++){
        st[i]=ed[i-1]+1;
        st[1]=0;
        ed[i]=st[i]+len-1;
    }
    ed[bl[N-4]]=N-4;
    q[q[i].t].ans=ask();

询问区间内有多少子序列满足异或和k。维护异或前缀和,即1到i的异或和,询问即在[l-1,r]内有多少数对异或为k,开通记录异或值,注意询问的l要减一

inline void add(int x){
    ans+=cnt[x^k];
    cnt[x]++;
}
inline void del(int x){
    cnt[x]--;
    ans-=cnt[x^k];
}

询问三个区间,把同时出现的数删掉,问最后剩下的数的个数和,询问独立。每个询问的答案是三个区间的区间长度和减去3*公共颜色的个数,bitset维护,在离散化时不要去重,这样bitset中每一位和后面一段区间都归属于这个数,在计算次数时&即可,由于询问过大,将所有询问拆成多组分开处理

#include<bits/stdc++.h>

using namespace std;

const int N=1e5+5,M=2e4+4;
int n,m,tot,bl[N],ans[N],cnt[N],a[N],b[N];
bitset<N>s[M],t;
bool vis[3*M];
struct query{
    int l,r,t;
    inline bool friend operator<(const query&a,const query&b){
        return bl[a.l]!=bl[b.l]?a.l<b.l:bl[a.l]&1?a.r<b.r:a.r>b.r;
    }
}q[M*3];
inline void add(int x){
    ++cnt[x];
    t[x+cnt[x]-1]=1;
}
inline void del(int x){
    t[x+cnt[x]-1]=0;
    --cnt[x];
}
inline void solve(int m){
    int tot=0;t.reset();
    memset(vis,0,sizeof(vis));
    memset(ans,0,sizeof(ans));
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=m;i++)for(int j=1;j<4;j++){
        int l,r;
        cin>>l>>r;
        q[++tot]={l,r,i};
        ans[i]+=r-l+1;
    }
    sort(q+1,q+1+tot);
    for(int i=1,l=1,r=0;i<=tot;i++){
        while(l>q[i].l)add(a[--l]);
        while(r<q[i].r)add(a[++r]);
        while(l<q[i].l)del(a[l++]);
        while(r>q[i].r)del(a[r--]);
        if(!vis[q[i].t]){
            vis[q[i].t]=1;
            s[q[i].t]=t;
        }
        else s[q[i].t]&=t;
    }
    for(int i=1;i<=m;i++)cout<<ans[i]-s[i].count()*3<<'\n';
}
signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    int len=sqrt(n);
    for(int i=1;i<=n;i++)cin>>a[i],b[i]=a[i],bl[i]=(i-1)/len+1;
    sort(b+1,b+1+n);
    for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+1+n,a[i])-b;
    int t=M-4;
    while(m){
        if(m<t)solve(m),m=0;
        else solve(t),m-=t;
    }
    return 0;
}
posted @ 2022-11-14 17:43  半步蒟蒻  阅读(22)  评论(0)    收藏  举报