- hsfzbzjr's blog
带修改的莫队
- 2023-2-12 11:55:35 @
U11讲 笔记
带修改的莫队算法
struct Query{int l,r,t,id;}q[M];
int nQ;
bool cmp(const Query&x,const Query&y){
if(b[x.l]!=b[y.l])return x.l<y.l;
if(b[x.r]!=b[y.r])return x.r<y.r;
return x.t<y.t;
}
表示 号问询依赖几次修改。
修改次数可以理解成时间戳
复杂度分析+最优分块大小
序列编号每组大小 ,序列共 组,平面共
移动 : 每两个问询间:左端点抖动距离不超过 ,左端点抖动总距离不超过
移动 : 块内每两个问询间:右端点抖动距离不超过 ,右端点抖动总距离不超过 ,跨越块列时总距离:
移动 : 每个块内总距离 : ,所有块总距离 :
总复杂度 : $2mB+ \frac{n^2}{2B}+ \frac{n^3}{2B^2} \ge n \sqrt[3]{2m^2} = O(n \sqrt[3]{m^2})$ 额外排序:
等号条件 : $B^3=\frac{n^3}{4m} , \quad B=\frac{n}{\sqrt[3]{4m}}$
例一 KK3170 计数种类 1
题面
题目描述
有一个长度为 的正整数序列,共有 次操作,分为两种
代表询问你从第 个数到第 个数中共有几种不同的数字类型。
把第 个数字替换为数值
程序名 : category.cpp
输入格式 category.in
第一行包含正整数 和 。
接着一行为 个正整数,代表序列的初始值。
然后有 行,每行一条操作指令,保证都合法。
输出格式 category.out
对于每一个问询,输出一行包含一个整数
输入样例
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
输出样例
4
4
3
4
数据范围
序列中元素的数值范围是 到
Code
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
const int M=1e4+10;
const int R=1e6+10;
struct Query{int l,r,t,id;}q[M];
int nQ;
bool cmp(const Query&x,const Query&y){
if(b[x.l]!=b[y.l])return x.l<y.l;
if(b[x.r]!=b[y.r])return x.r<y.r;
return x.t<y.t;
}
struct Update{int p,v1,v2;}upds[M];
int nU;
int n,m,L;
int cnt[R];
int a[N];
int main(){
freopen("category.in","r",stdin);
freopen("category.out","w",stdout);
cin>>n>>m;
L=cbrt(n*n);
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=(i-1)/L+1;
}
for(int i=1;i<=m;i++){
char op;
int x,y,p,v;
cin>>op;
if(op=='Q'){
++nQ;
cin>>q[nQ].l>>q[nQ].r;
q[nQ].t=nU;
q[nQ].id=nQ;
}else{
++nU;
cin>>upds[nU].p>>upds[nU].v2;
upds[nU].v1=a[upds[nU].p];
a[upds[nU].p]=upds[nU].v2;
}
}
sort(q+1,q+1+nQ,cmp);
int l=1,r=0;
int ans=0;
int t=nU;
for(int i=1;i<=nQ;i++){
while(q[i].l>l){
--cnt[a[l]];
ans-=(cnt[a[l]]==0);
l++;
}
while(q[i].l<l){
--l;
++cnt[a[l]];
ans+=(cnt[a[l]]==1);
}
while(q[i].r<r){
--cnt[a[r]];
ans-=(cnt[a[r]]==0);
r--;
}
while(q[i].r>r){
++r;
++cnt[a[r]];
ans+=(cnt[a[r]]==1);
}
while(q[i].t>t){
++t;
if(l<=upds[t].p&&upds[t].p<=r){
--cnt[a[upds[t]].p];
ans-=(cnt[a[upds[t].p]]==0);
}
a[upds[t].p]=upds[t].v2;
if(l<=upds[t].p&&upds[t].p<=r){
++cnt[a[upds[t].p]];
ans+=(cnt[a[upds[t].p]]==1);
}
}
while(q[i].t<t){
if(l<=upds[t].p&&upds[t].p<=r){
--cnt[a[upds[t]].p];
ans-=(cnt[a[upds[t].p]]==0);
}
a[upds[t].p]=upds[t].v1;
if(l<=upds[t].p&&upds[t].p<=r){
++cnt[a[upds[t].p]];
ans+=(cnt[a[upds[t].p]]==1);
}
--t;
}
ANS[q[i].id]=ans;
}
for(int i=1;i<=nQ;i++)
cout<<ANS[i]<<endl;
}
封装操作函数
void add(int v){
ans+=((++cnt[v])==1);
}
void rmv(int v){
ans-=((--cnt[v])==0);
}
void upd(int iQ,int iU,int tag){
if(q[iQ].l<=upds[iU].p&&upds[iU].p<=q[iQ].r)
rmv(a[upds[iU].p]);
a[upds[iU].p]=(tag==1?upds[iU].v1:upds[iU].v2);
if(q[iQ].l<=upds[iU].p&&upds[iU].p<=q[iQ].r)
add(a[upds[iU].p]);
}
封装后的核心代码
sort(q+1,q+1+nQ,cmp);
int l=1,r=0;
int t=nU;
for(int i=1;i<=nQ;i++){
while(q[i].l>l)rmv(a[l++]);
while(q[i].l<l)add(a[--l]);
while(q[i].r<r)rmv(a[r--]);
while(q[i].r>r)add(a[++r]);
while(q[i].t>t)upd(i,++t,2);
while(q[i].t<t)upd(i,t--,1);
ANS[q[i].id]=ans;
}
简化修改操作
修改只有两种可能:正向修改/反向撤销
且一定是交替发生
struct Update{int p,v;}upds[M];
int nU;
void upd(int iQ,int iU){
if(q[iQ].l<=upds[iU].p&&upds[iU].p<=q[iQ].r)
rmv(a[upds[iU].p]);
swap(a[upds[iU].p],upds[iU].v);
if(q[iQ].l<=upds[iU].p&&upds[iU].p<=q[iQ].r)
add(a[upds[iU].p]);
}
for(int i=1;i<=m;i++){
char op;
int x,y,p,v;
cin>>op;
if(op=='Q'){
}
else{
++nU;
cin>>upds[nU].p>>upds[nU].v;
}
}
int t=0;
int l=1,r=0;
int ans=0;
for(int i=1;i<=nQ;i++){
while(q[i].l>l)rmv(a[l++]);
while(q[i].l<l)add(a[--l]);
while(q[i].r<r)rmv(a[r--]);
while(q[i].r>r)add(a[++r]);
while(q[i].t>t)upd(i,++t);
while(q[i].t<t)upd(i,t--);
ANS[q[i].id]=ans;
}
例二 KK3171 计数种类2
题面
题目描述
有一个长度为 的正整数数列,共有 次操作,分为两种:
代表问询你从第 个数到第 个数中出现次数所组成数集的 值
把第 个数字替换成数值
备注:在这里,一个整数集合的 值为最小的不在集合中的正整数
输入格式 classification.in
第一行包含正整数 和 。
接着一行为 个正整数,代表序列的初始值。
然后有 行,每行一条操作指令,保证都合法。
输出格式 classification.out
对于每一个问询,输出一行包含一个整数。
输入样例
10 4
1 2 3 1 1 2 2 2 9 9
1 1 1
1 2 8
2 7 1
1 2 8
输出样例
2
3
2
数据范围
数值范围是 到
算法分析
引理
答案范围不超过
证明
假设某一次问询的答案为 ,大于 ,那么根据 的定义,这段区间中数的个数至少为 $O(\sum_{i=1}^{i<=x} i) > O(\sum_{i=1}^{i<=\sqrt{n}} i) = O(n)$ 但是区间长度不能超过 。因此假设不成立。=x}>
因此,答案并不会很大,可以维护一个计数器的计数器数组。
Code
离散化
for(int i=1;i<=n;i++)as[i]=a[i];
for(int i=1;i<=nU;i++)as[i+n]=upds[i].v;
sort(as+1,as+1+n+nU);
for(int i=1;i<=n;i++)
a[i]=lower_bound(as+1,as+1+n+nU,a[i])-as;
for(int i=1;i<=nU;i++)
upds[i].v=lower_bound(as+1,as+1+n+nU,upds[i].v)-as;
核心代码
sort(q+1,q+1+nQ,cmp);
int l=1,r=0;
int t=0;
for(int i=1;i<=nQ;i++){
while(q[i].l>l)rmv(a[l++]);
while(q[i].l<l)add(a[--l]);
while(q[i].r<r)rmv(a[r--]);
while(q[i].r>r)add(a[++r]);
while(q[i].t>t)upd(i,++t);
while(q[i].t<t)upd(i,t--);
for(int c=1;1;c++){
if(ccnt[c])continue;
ANS[q[i].id]=c;
break;
}
}
三个封装的函数
void add(int v){
--ccnt[cnt[v]];
++cnt[v];
++ccnt[cnt[v]];
}
void rmv(int v){
--ccnt[cnt[v]];
--cnt[v];
++ccnt[cnt[v]];
}
void upd(int iQ,int iU){
if(q[iQ].l<=upds[iU].p&&upds[iU].p<=q[iQ].r)
rmv(a[upds[iU].p]);
swap(a[upds[iU].p],upds[iU].v);
if(q[iQ].l<=upds[iU].p&&upds[iU].p<=q[iQ].r)
add(a[upds[iU].p]);
}
Homework
KK3170,KK3170