根号算法
一、分块
我们发现,如果一片区域被完全覆盖,我们可以对这个整块打上标记,而如果没有完全覆盖,我们只能挨个加。设我们将序列平均分成了
虽然这没有线段树快,但其能维护更复杂的信息,并且单点修改是
二、根号分治
CF797E Array Queries
给出数组
a ,对此询问k,p ,求至少经过多少次p\gets p+a_p+k 才能使得p>n 。
可以发现,如果暴力跳是
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,sqn,a[N];
int dp[N][320];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;sqn=sqrt(n);
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=n;i>=1;--i){
for(int j=1;j<=sqn;++j){
if(i+a[i]+j>n)dp[i][j]=1;
else dp[i][j]=dp[i+a[i]+j][j]+1;
}
}cin>>m;
while(m--){
int p,k,ans=0;cin>>p>>k;
if(k<=sqn){
cout<<dp[p][k]<<'\n';
}
else{
while(p<=n)p+=a[p]+k,++ans;
cout<<ans<<'\n';
}
}
return 0;
}
CF1580C Train Maintenance
比较明显的根号分治。如果
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,sqn,x[N],y[N];
int las[N],b[N],st[500][500];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;sqn=sqrt(n);
for(int i=1;i<=n;++i)
cin>>x[i]>>y[i];
for(int i=1,op,p,ans;i<=m;++i){
ans=0;cin>>op>>p;
int z=x[p]+y[p];
if(op==1){
las[p]=i;
if(z<=sqn)
for(int j=0;j<z;++j)st[z][(i+j)%z]+=(j>=x[p]);
else
for(int j=i+x[p];j<=m;j+=z)++b[j],--b[min(m+1,j+y[p])];
}
else{
if(z<=sqn)
for(int j=0;j<z;++j)st[z][(las[p]+j)%z]-=(j>=x[p]);
else
for(int j=las[p]+x[p];j<=m;j+=z)--b[max(i,j)],++b[max(i,min(m+1,j+y[p]))];
}
for(int j=2;j<=sqn;++j)
ans+=st[j][i%j];
b[i]+=b[i-1];
cout<<ans+b[i]<<'\n';
}
return 0;
}
P6189 [NOI Online #1 入门组] 跑步
正整数拆分问题。
有一个很明显的 DP,
令
设
最终答案为
这样总复杂度为
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,Q=320;
int n,m,p,f[Q][N],g[Q][N];
int ans=0;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>p;const int P=p;
int m=sqrt(n);f[0][0]=g[0][0]=1;
for(int i=1;i<m;++i){
for(int j=0;j<=n;++j){
f[i][j]=f[i-1][j];
if(j>=i)(f[i][j]+=f[i][j-i])%=P;
}
}
for(int i=1;i<m;++i){
for(int j=0;j<=n;++j){
if(j>=m)(g[i][j]+=g[i-1][j-m])%=P;
if(j>=i)(g[i][j]+=g[i][j-i])%=P;
}
}
for(int i=0;i<=n;++i){
int s=0;for(int j=0;j<m;++j)(s+=g[j][n-i])%=P;
ans=(ans+1ll*f[m-1][i]*s)%P;
}cout<<ans<<'\n';
return 0;
}
三、莫队
普通莫队——P3901 数列找不同
多次判断某个区间的数是否互不相同。
只讲莫队的方法。题目相当于判断区间内不同数字的个数是否等于区间长度。我们发现,如果我们记录了区间
但是我们可以轻松卡掉这种做法,原因是我们的
考虑根号平衡,将长度为
不理解可以先记着排序的方式,会用就行。
#include<bits/stdc++.h>
using namespace std;
const int N=100005;
struct question{int l,r,id;}q[N];
int tong[N],a[N],p1=0,p2=0;
int cnt=0,ans[N],s;
inline bool cmp(question x,question y){
return (x.l/s==y.l/s)?x.r<y.r:x.l<y.l;
}inline void add(int x){
if((++tong[a[x]])==1)cnt++;
}inline void era(int x){
if((--tong[a[x]])==0)cnt--;
}signed main(){
std::ios::sync_with_stdio(false);
cin.tie(NULL);cout.tie(NULL);
register int n,m,i,j;
cin>>n>>m;
s=sqrt(n);
for(i=1;i<=n;i++)
cin>>a[i];
for(i=1;i<=m;i++)
cin>>q[i].l>>q[i].r,q[i].id=i;
sort(q+1,q+m+1,cmp);
for(i=1;i<=m;i++){
int L=q[i].l;
int R=q[i].r;
while(p1>L)p1--,add(p1);
while(p1<L)era(p1),p1++;
while(p2>R)era(p2),p2--;
while(p2<R)p2++,add(p2);
if(cnt==R-L+1)
ans[q[i].id]=1;
}
for(int i=1;i<=m;i++)
if(ans[i]==1)cout<<"Yes\n";
else cout<<"No\n";
return 0;
}
作者若干年前的丑代码。
P1494 [国家集训队] 小 Z 的袜子
维护区间内有多少对相同的数字。
在上一题的基础上,记录一个总答案,设加入数
#include<bits/stdc++.h>
#define pb push_back
#define ll long long
using namespace std;
const int N=50005,Q=223;
int n,m,a[N],st[N];
ll ans[N],gty[N],res;
struct ques{int l,r,id;}q[N];
void add(int x){res+=st[x];++st[x];}
void era(int x){--st[x];res-=st[x];}
bool cmp(ques x,ques y){
return x.l/Q==y.l/Q?x.r<y.r:x.l<y.l;
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1,j;i<=m;++i)
cin>>q[i].l>>q[i].r,j=q[i].r-q[i].l+1,
gty[q[i].id=i]=1ll*j*(j-1)/2;
sort(q+1,q+m+1,cmp);
for(int i=1,l=1,r=0;i<=m;++i){
int L=q[i].l,R=q[i].r;
while(l>L)add(a[--l]);while(r<R)add(a[++r]);
while(l<L)era(a[l++]);while(r>R)era(a[r--]);
ans[q[i].id]=res;
}for(int i=1;i<=m;++i){
if(!ans[i])cout<<"0/1\n";
else{
int d=__gcd(ans[i],gty[i]);
cout<<ans[i]/d<<'/'<<gty[i]/d<<'\n';
}
}
return 0;
}
P3730 曼哈顿交易
多次询问区间相同数字出现次数的第
k 小。
如果只是区间第
我们不需要线段树的
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,SN=320;
int n,m,sqn,nqs;
int a[N],lsh[N];
struct ques{int l,r,k,id;}q[N];
inline bool cmp(ques a,ques b){return(a.l/sqn==b.l/sqn?a.r<b.r:a.l<b.l);}
int block[N],le[SN],ri[SN];
int st1[N],st2[N],st3[SN];
int ans[N];
inline void add(int x){
--st2[st1[x]];
--st3[block[st1[x]]];
++st1[x];
++st2[st1[x]];
++st3[block[st1[x]]];
}
inline void del(int x){
--st2[st1[x]];
--st3[block[st1[x]]];
--st1[x];
++st2[st1[x]];
++st3[block[st1[x]]];
}
inline int solve(int k){
int i;
for(i=1;i<=sqn;++i)
if(k<=st3[i])break;
else k-=st3[i];
if(i==sqn+1)return -1;
for(int j=le[i];j<=ri[i];++j)
if(k<=st2[j])return j;
else k-=st2[j];
return -1;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
sqn=sqrt(n);
for(int i=1;i<=n;++i)
cin>>a[i],lsh[i]=a[i];
sort(lsh+1,lsh+n+1);
int cnt=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;++i)
a[i]=lower_bound(lsh+1,lsh+cnt+1,a[i])-lsh;
for(int i=1,num=-1;i<=n;++i){
block[i]=(i-1)/sqn+1;
if(num^block[i])
le[++nqs]=i,num=nqs;
ri[nqs]=i;
}
for(int i=1;i<=m;++i)
cin>>q[i].l>>q[i].r>>q[i].k,q[i].id=i;
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;++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--]);
ans[q[i].id]=solve(q[i].k);
}
for(int i=1;i<=n;++i)
cout<<ans[i]<<'\n';
return 0;
}
带修莫队——P1903 [国家集训队] 数颜色 / 维护队列
除了区间的变化,我们多了一维时间线。
我们能否从状态
那么怎么排序呢?取
#include<bits/stdc++.h>
using namespace std;
const int N=133338,M=1e6+5,Q=2600;
int n,m,k,a[N],p[N],c[N],ans[N];
struct ques{int l,r,t,id;}q[N];
bool cmp(ques x,ques y){
return x.l/Q==y.l/Q?x.r/Q==y.r/Q?x.t<y.t:x.r<y.r:x.l<y.l;
}int st[M],cnt=0;
void add(int x){cnt+=!st[x]++;}
void del(int x){cnt-=!--st[x];}
void upd(int x,int y){
if(q[x].l<=p[y]&&p[y]<=q[x].r)
del(a[p[y]]),add(c[y]);
swap(a[p[y]],c[y]);
}signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1,j=0;i<=m;++i){
char op;cin>>op;
if(op=='R')++j,cin>>p[j]>>c[j];
else ++k,cin>>q[k].l>>q[k].r,q[k].t=j,q[k].id=k;
}sort(q+1,q+k+1,cmp);
for(int i=1,l=1,r=0,t=0;i<=k;++i){
int L=q[i].l,R=q[i].r,T=q[i].t;
while(l>L)add(a[--l]);while(r<R)add(a[++r]);
while(l<L)del(a[l++]);while(r>R)del(a[r--]);
while(t<T)upd(i,++t);while(t>T)upd(i,t--);
ans[q[i].id]=cnt;
}for(int i=1;i<=k;++i)
cout<<ans[i]<<'\n';
return 0;
}
回滚莫队——P5906 【模板】回滚莫队
有些时候,我们会发现添加容易,但是删除很困难。
在讨论莫队复杂度时,我们发现
对于这一题,我们维护每个数最左和最右的位置,这种信息添加是容易的。如果
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,sqn,a[N];
int lsh[N],len;
int block[N];
struct ques{int l,r,id;}q[N];
inline bool cmp(ques x,ques y){
return (block[x.l]==block[y.l]?x.r<y.r:x.l<y.l);
}int ans[N];
int t[N],pre[N];
int mx1[N],mn1[N];
int mx2[N],mn2[N];
int mx3[N],mn3[N];
int res1,res2;
inline void add1(int x){
mx2[a[x]]=mx1[a[x]]=max(mx1[a[x]],x);
mn2[a[x]]=mn1[a[x]]=min(mn1[a[x]],x);
res2=res1=max(res1,mx1[a[x]]-mn1[a[x]]);
}
inline void add2(int x){
mx2[a[x]]=max(mx2[a[x]],x);
mn2[a[x]]=min(mn2[a[x]],x);
res2=max(res2,mx2[a[x]]-mn2[a[x]]);
}
inline void era(int x){
mx2[a[x]]=mx1[a[x]];
mn2[a[x]]=mn1[a[x]];
res2=res1;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;sqn=sqrt(n);
for(int i=1;i<=n;++i)
cin>>a[i],lsh[i]=a[i];
sort(lsh+1,lsh+n+1);
len=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;++i)
a[i]=lower_bound(lsh+1,lsh+len+1,a[i])-lsh;
for(int i=1;i<=n;++i)
block[i]=(i-1)/sqn+1;
cin>>m;
for(int i=1;i<=m;++i)
cin>>q[i].l>>q[i].r,q[i].id=i;
sort(q+1,q+m+1,cmp);
memset(mn1,0x3f,sizeof(mn1));
memset(mn2,0x3f,sizeof(mn2));
memset(mn3,0x3f,sizeof(mn3));
for(int i=1,l=1,r=0,bl=0,br=0;i<=m;++i){
int L=q[i].l,R=q[i].r;
if(block[R]-block[L]<=1){
int res=0;
for(int j=L;j<=R;++j){
mx3[a[j]]=max(mx3[a[j]],j);
mn3[a[j]]=min(mn3[a[j]],j);
res=max(res,mx3[a[j]]-mn3[a[j]]);
}
ans[q[i].id]=res;
for(int j=L;j<=R;++j)
mx3[a[j]]=0,mn3[a[j]]=0x3f3f3f3f;
}
else{
if(block[L]!=bl){
for(int j=1;j<=len;++j)
mx1[j]=mx2[j]=0,mn1[j]=mn2[j]=0x3f3f3f3f;
res1=res2=0;
bl=block[L];
r=br=min(bl*sqn,n);
l=r+1;
}
while(r<R)add1(++r);
while(l>L)add2(--l);
ans[q[i].id]=res2;
while(l<=br)era(l++);
res2=res1;
}
}
for(int i=1;i<=m;++i)
cout<<ans[i]<<'\n';
return 0;
}
P3709 大爷的字符串题
求区间众数出现的次数。
这个转化比较特别,我们可以将这个区间重排成尽可能少的严格递增序列,这样是最优的,而答案就是区间众数出现次数。
我们添加是好添加的,然而删除我们不知道次大的是什么,如果用 set 则又要多个
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,sqn,a[N];
int lsh[N],len;
int block[N];
struct ques{int l,r,id;}q[N];
inline bool cmp(ques x,ques y){
return (block[x.l]==block[y.l]?x.r<y.r:x.l<y.l);
}int ans[N];
int t[N],pre[N];
int st1[N],st2[N],st3[N];
int res1,res2;
inline void add1(int x){
st2[x]=st1[x]=++st1[x];
res2=res1=max(res1,st1[x]);
}
inline void add2(int x){
st2[x]=++st2[x];
res2=max(res2,st2[x]);
}
inline void era(int x){
st2[x]=st1[x];
res2=res1;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;sqn=sqrt(n);
for(int i=1;i<=n;++i)
cin>>a[i],lsh[i]=a[i];
sort(lsh+1,lsh+n+1);
len=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;++i)
a[i]=lower_bound(lsh+1,lsh+len+1,a[i])-lsh;
for(int i=1;i<=n;++i)
block[i]=(i-1)/sqn+1;
for(int i=1;i<=m;++i)
cin>>q[i].l>>q[i].r,q[i].id=i;
sort(q+1,q+m+1,cmp);
for(int i=1,l=1,r=0,bl=0,br=0;i<=m;++i){
int L=q[i].l,R=q[i].r;
if(block[R]-block[L]<=1){
int res=0;
for(int j=L;j<=R;++j){
++st3[a[j]];
res=max(res,st3[a[j]]);
}
ans[q[i].id]=res;
for(int j=L;j<=R;++j)
st3[a[j]]=0;
}
else{
if(block[L]!=bl){
for(int j=1;j<=len;++j)
st1[j]=st2[j]=0;
res1=res2=0;
bl=block[L];
r=br=min(bl*sqn,n);
l=r+1;
}
while(r<R)add1(a[++r]);
while(l>L)add2(a[--l]);
ans[q[i].id]=res2;
while(l<=br)era(a[l++]);
res2=res1;
}
}
for(int i=1;i<=m;++i)
cout<<-ans[i]<<'\n';
return 0;
}
莫队二离——P4887 【模板】莫队二次离线
有时,我们从
以这题为例,我们要查询若干个
for(int i=0;i<16384;++i)
if(__builtin_popcount(i)==k)
base.push_back(i);
for(int i=1;i<=n;++i){
for(auto x:base)
++t[a[i]^x];
pre[i]=t[a[i+1]];
}
这里
我们对答案差分,这样我们只用处理相邻两个答案的变化量。我们共有
其中部分是直接能用
特判
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m,k,a[N];
int sqn,ans[N];
struct ques{int l,r,id;}q[N];
inline bool cmp(ques x,ques y){
return (x.l/sqn==y.l/sqn?x.r<y.r:x.l<y.l);
}
vector<int> base;
vector<tuple<int,int,int> >v[N];
int t[N],pre[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>k;
if(k>14){
for(int i=1;i<=m;++i)
cout<<"0\n";
return 0;
}sqn=sqrt(n);
for(int i=1;i<=n;++i)
cin>>a[i];
for(int i=1;i<=m;++i)
cin>>q[i].l>>q[i].r,q[i].id=i;
sort(q+1,q+m+1,cmp);
for(int i=0;i<16384;++i)
if(__builtin_popcount(i)==k)
base.push_back(i);
for(int i=1;i<=n;++i){
for(auto x:base)
++t[a[i]^x];
pre[i]=t[a[i+1]];
}memset(t,0,sizeof(t));
for(int i=1,l=1,r=0;i<=n;++i){
int L=q[i].l,R=q[i].r;
if(l<L)v[r].emplace_back(l,L-1,-i);
while(l<L)ans[q[i].id]+=pre[l-1],++l;
if(l>L)v[r].emplace_back(L,l-1,i);
while(l>L)ans[q[i].id]-=pre[l-2],--l;
if(r<R)v[l-1].emplace_back(r+1,R,-i);
while(r<R)ans[q[i].id]+=pre[r],++r;
if(r>R)v[L-1].emplace_back(R+1,r,i);
while(r>R)ans[q[i].id]-=pre[r-1],--r;
}
for(int i=1,id,l,r;i<=n;++i){
for(auto x:base)++t[a[i]^x];
for(const auto& x:v[i]){
tie(l,r,id)=x;
for(int j=l,tmp=0;j<=r;++j){
tmp=t[a[j]];
if(j<=i&&k==0)--tmp;
if(id<0)ans[q[-id].id]-=tmp;
else ans[q[id].id]+=tmp;
}
}
}
for(int i=1;i<=m;++i)
ans[q[i].id]+=ans[q[i-1].id];
for(int i=1;i<=m;++i)
cout<<ans[i]<<'\n';
return 0;
}
P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II
我们需要在莫队的基础上求出区间
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+5,sqn=320;
ll c[N];
void add(int x){while(x<N)++c[x],x+=x&-x;}
ll ask(int x){int k=0;while(x)k+=c[x],x-=x&-x;return k;}
int n,m;
int a[N],lsh[N],cnt;
ll pre[N],suf[N],ans[N];
struct ques{int l,r,id;}q[N];
inline bool cmp(ques x,ques y){
return x.l/sqn==y.l/sqn?x.r<y.r:x.l<y.l;
}
struct node{int id,l,r,v;};
vector<node>v1[N],v2[N];
inline void init(){
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>a[i],lsh[i]=a[i];
sort(lsh+1,lsh+n+1);
cnt=unique(lsh+1,lsh+n+1)-lsh-1;
for(int i=1;i<=n;++i)
a[i]=lower_bound(lsh+1,lsh+cnt+1,a[i])-lsh;
for(int i=1;i<=n;++i)
pre[i]=pre[i-1]+i-1-ask(a[i]),add(a[i]);
memset(c,0,sizeof(c));
for(int i=n;i>=1;--i)
suf[i]=suf[i+1]+ask(a[i]-1),add(a[i]);
for(int i=1;i<=m;++i)
cin>>q[i].l>>q[i].r,q[i].id=i;
sort(q+1,q+m+1,cmp);
}
int id[N],le[N],ri[N];
int tag[N],w[N];
inline void solve1(){
for(int i=1,l=1,r=0;i<=m;++i){
int L=q[i].l,R=q[i].r,id=q[i].id;
if(r<R)ans[q[i].id]+=(pre[R]-pre[r]),
v1[l].push_back((node){id,r+1,R,-1}),r=R;
if(r>R)ans[q[i].id]-=(pre[r]-pre[R]),
v1[l].push_back((node){id,R+1,r,1}),r=R;
if(l<L)ans[q[i].id]-=(suf[l]-suf[L]),
v2[r].push_back((node){id,l,L-1,1}),l=L;
if(l>L)ans[q[i].id]+=(suf[L]-suf[l]),
v2[r].push_back((node){id,L,l-1,-1}),l=L;
}
}
inline void solve2(){
int num=sqrt(cnt),cur=1;le[1]=1;
if(num*num<cnt)++num;
for(int i=1;i<=cnt;++i){
id[i]=cur;
if(i%num==0)ri[cur]=i,le[++cur]=i+1;
}ri[cur]=cnt;
for(int i=1;i<=n;++i){
for(int j=0;j<v1[i].size();++j){
int L=v1[i][j].l,R=v1[i][j].r,v=v1[i][j].v;
for(int k=L;k<=R;++k)
ans[v1[i][j].id]+=1ll*v*(tag[id[a[k]+1]]+w[a[k]+1]);
}
int x=a[i];
for(int i=le[id[x]];i<=x;++i)++w[i];
for(int i=1;i<id[x];++i)++tag[i];
}
memset(tag,0,sizeof(tag));
memset(w,0,sizeof(w));
for(int i=n;i>=1;--i){
for(int j=0;j<v2[i].size();++j){
int L=v2[i][j].l,R=v2[i][j].r,v=v2[i][j].v;
for(int k=L;k<=R;++k)
ans[v2[i][j].id]+=1ll*v*(tag[id[a[k]-1]]+w[a[k]-1]);
}
int x=a[i];
for(int i=x;i<=ri[id[x]];++i)++w[i];
for(int i=id[x]+1;i<=num;++i)++tag[i];
}
}
inline void out(){
for(int i=1;i<=m;++i)ans[q[i].id]+=ans[q[i-1].id];
for(int i=1;i<=m;++i)cout<<ans[i]<<'\n';
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
init();solve1();solve2();out();
return 0;
}
四、简单习题。
P3396 哈希冲突:根号分治
P5501 [LnOI2019] 来者不拒,去者不追:莫队二离
P4396 [AHOI2013] 作业:莫队
P7708 「Wdsr-2.7」八云蓝自动机 Ⅰ:莫队维护操作序列