静态莫队
莫队,对询问离线并进行合理的排序,处理完一个区间的询问后,可以以常数的时间复杂度转移到下一个询问的答案,通常初始化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;
}

浙公网安备 33010602011771号