莫队
基础
- 分块
- 多关键字排序
- 倍增树剖lca(树上莫队)
- 数值离散化
HH的项链(SPOJ3267)
题意:
给定长度为n的序列,m次询问,每次询问一个区间有多少种不同的数
思路:
- 树状数组:离线下来,然后只维护最后出现的数,前缀和查询即可
- 主席树:在线询问,一样的思路维护最后出现的数,查询右端点对应的版本的[l,r]即可
莫队
- 离线询问
- 首先考虑暴力怎么做
- 对于每次询问,移动左右指针,开一个桶记录数的出现次数,复杂度:
- 分块,然后区间如果左端点块编号不同,则按右端点排序,否则按左端点对应块编号排序
- 假设块长为 ,对于同一个块内右端点最多移动 次,有 个,复杂度为
- 对于块编号相同的左端点,最多移动次数为 次, 次询问,因此最多移动次数为
- 对于块编号不同的左端点块间距离为 ,一共 移动 次
- 一般 取 即可,此处让
cpp
struct Node{
int id,l,r;
};
int cnt[N],w[M];
int be[M],L[M],R[M],tot,block,res;
bool cmp(Node x,Node y){
return (be[x.l]^be[y.l])?be[x.l]<be[y.l]:((be[x.l]&1)?x.r<y.r:x.r>y.r);
//奇数块从小到大,偶数块相反
}
void add(int x){
if(!(cnt[w[x]]++))res++;
}
void del(int x){
if(!(--cnt[w[x]]))res--;
}
void solve(){
int n,m;
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
cin>>m;
vector<Node> q(m);
vector<int> ans(m);
for(int i=0;i<m;i++)cin>>q[i].l>>q[i].r,q[i].id=i;
block=sqrt((double)n*n/m),tot=(n+block-1)/block;
for(int i=1;i<=tot;i++){
int l=(i-1)*block+1,r=min(i*block,n);
for(int j=l;j<=r;j++)be[j]=i;
}
sort(q.begin(),q.end(),cmp);
for(int k=0,l=1,r=0;k<m;k++){
auto [id,ql,qr]=q[k];
while(r<qr)add(++r);
while(r>qr)del(r--);
while(l<ql)del(l++);
while(l>ql)add(--l);
ans[id]=res;
}
for(int i=0;i<m;i++)cout<<ans[i]<<"\n";
}P1903 [国家集训队] 数颜色 / 维护队列(带修莫队)
题意:
原来的基础上,加上修改某个数的操作
思路:
- 二关键字变三关键字
- 首先按l的块编号排序,再按r的块编号排序,最后按修改版本t排序
- 对于l指针,块内移动为 ,总次数为 ,块间移动为 ,总复杂度为
- 对于r指针,块内移动为 ,块间移动为
- 对于t指针,移动次数为
- dalao证明 最优
- 块长取 更优,总复杂度为
- 修改操作优化,将原数组的数与修改数交换即可
cpp
struct Node{
int id,l,r,t;
};
int cnt[N],w[M];
int be[M],L[M],R[M],tot,block,res;
bool cmp(Node x,Node y){
return (be[x.l]^be[y.l])?be[x.l]<be[y.l]:((be[x.r]^be[y.r]?be[x.r]<be[y.r]:x.t<y.t));
}
void add(int x){
if(!(cnt[x]++))res++;
}
void del(int x){
if(!(--cnt[x]))res--;
}
void solve(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>w[i];
vector<Node> q;
vector<array<int,2>> c={{0,0}};
int que=0;
for(int i=0;i<m;i++){
char op;
int x,y;
cin>>op>>x>>y;
if(op=='Q'){
q.push_back({que++,x,y,c.size()-1});
}
else{
c.push_back({x,y});
}
}
vector<int> ans(que);
block=pow(n,2.0/3.0),tot=(n+block-1)/block;
for(int i=1;i<=tot;i++){
int l=(i-1)*block+1,r=min(i*block,n);
for(int j=l;j<=r;j++)be[j]=i;
}
sort(q.begin(),q.end(),cmp);
for(int i=0,l=1,r=0,t=0;i<q.size();i++){
auto [id,ql,qr,qt]=q[i];
while(r<qr)add(w[++r]);
while(r>qr)del(w[r--]);
while(l<ql)del(w[l++]);
while(l>ql)add(w[--l]);
while(t<qt){
t++;
if(ql<=c[t][0]&&qr>=c[t][0])del(w[c[t][0]]),add(c[t][1]);
swap(w[c[t][0]],c[t][1]);
}
while(t>qt){
if(ql<=c[t][0]&&qr>=c[t][0])del(w[c[t][0]]),add(c[t][1]);
swap(w[c[t][0]],c[t][1]);
t--;
}
ans[id]=res;
}
for(auto it:ans)cout<<it<<"\n";
}歴史の研究(回滚莫队)
题意:
一个数的重要度为数值*出现次数,每次询问一个区间内的最重要度
思路:
- 按左端点块编号排序,再按右端点排序
- 对于左右端点在同一块内的询问,暴力询问
- 对于不在同一块内的区间询问,固定当前右指针为块的右边,因为块内区间右端点升序,因此右指针单调移动,最多为 次,总次数为 次
- 然后暴力移动左端点,每个块最多为 次,一共 次
- B取
cpp
struct Node{
int id,l,r;
};
int w[M];
int be[M],L[M],R[M],tot,block;
bool cmp(Node x,Node y){
return (be[x.l]^be[y.l])?be[x.l]<be[y.l]:x.r<y.r;
}
void solve(){
int n,m;
cin>>n>>m;
vector<int> nums;
for(int i=1;i<=n;i++)cin>>w[i],nums.push_back(w[i]);
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());
for(int i=1;i<=n;i++)w[i]=lower_bound(nums.begin(),nums.end(),w[i])-nums.begin();
vector<int> cnt(nums.size());
auto add=[&](int x,int &res){
cnt[x]++;
res=max(res,cnt[x]*nums[x]);
};
vector<int> ans(m+1);
vector<Node> q(m+1);
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
block=sqrt(1.0*n*n/m),tot=(n+block-1)/block;
for(int i=1;i<=tot;i++){
L[i]=(i-1)*block+1,R[i]=min(i*block,n);
for(int j=L[i];j<=R[i];j++)be[j]=i;
}
sort(q.begin()+1,q.end(),cmp);
for(int i=1,j;i<=m;i++){
j=i;
int right=R[be[q[i].l]];
while(j+1<=m&&be[q[i].l]==be[q[j+1].l])j++;
while(i<=j&&q[i].r<=right){
//左右端点在同一块
auto [id,ql,qr]=q[i++];
int res=0;
for(int k=ql;k<=qr;k++)add(w[k],res);
ans[id]=res;
for(int k=ql;k<=qr;k++)cnt[w[k]]--;//复原
}
int l=right+1,r=right;
int res=0;
while(i<=j){
auto [id,ql,qr]=q[i++];
while(r<qr)add(w[++r],res);
int backup=res;
while(l>ql)add(w[--l],res);
ans[id]=res;
res=backup;
while(l<right+1)cnt[w[l++]]--;
}
while(r>right)cnt[w[r--]]--;
i=j;
}
for(int i=1;i<=m;i++)cout<<ans[i]<<"\n";
}P5906 【模板】回滚莫队&不删除莫队
题意:
询问区间内最大的相同数之间的间距
思路:
- 回滚莫队一样的套路
- 注意:维护mx,mn数组,使用栈还原
cpp
int be[M],L[M],R[M],block,tot;
struct Q{
int id,l,r;
};
bool cmp(Q &x,Q &y){
return (be[x.l]^be[y.l])?be[x.l]<be[y.l]:x.r<y.r;
}
void solve(){
int n;
cin>>n;
vector<int> nums,w(n+1);
for(int i=1;i<=n;i++)cin>>w[i],nums.push_back(w[i]);
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());
for(int i=1;i<=n;i++)w[i]=lower_bound(nums.begin(),nums.end(),w[i])-nums.begin();
vector<int> mx(nums.size()),mn(nums.size());
int m;
cin>>m;
vector<Q> q;
vector<int> ans(m);
for(int i=0;i<m;i++){
int l,r;
cin>>l>>r;
q.push_back({i,l,r});
}
block=sqrt(n),tot=(n+block-1)/block;
for(int i=1;i<=tot;i++){
L[i]=(i-1)*block+1,R[i]=min(n,i*block);
for(int j=L[i];j<=R[i];j++)be[j]=i;
}
sort(q.begin(),q.end(),cmp);
vector<array<int,3>> st;
auto add=[&](int x,int &res){
st.push_back({w[x],mn[w[x]],mx[w[x]]});
if(!mx[w[x]])mx[w[x]]=mn[w[x]]=x;
else mx[w[x]]=max(mx[w[x]],x),mn[w[x]]=min(mn[w[x]],x);
res=max(res,mx[w[x]]-mn[w[x]]);
};
auto del=[&](){
auto [val,MN,MX]=st.back();
st.pop_back();
mn[val]=MN,mx[val]=MX;
};
for(int i=0,j;i<m;i++){
j=i;
while(j+1<m&&be[q[j].l]==be[q[j+1].l])j++;
int right=R[be[q[i].l]];
while(i<=j&&q[i].r<=right){
auto [id,ql,qr]=q[i++];
int res=0;
for(int k=ql;k<=qr;k++)add(k,res);
ans[id]=res;
while(st.size())del();
}
int res=0,l=right+1,r=right;
while(i<=j){
auto [id,ql,qr]=q[i++];
while(r<qr)add(++r,res);
int backup=res;
while(l>ql)add(--l,res);
ans[id]=res;
res=backup;
while(l<right+1)l++,del();
}
while(st.size())del();
i=j;
}
for(int i=0;i<m;i++)cout<<ans[i]<<"\n";
}SP10707树上计数2(树上莫队板子)
题意:
给定一颗树,每次询问u,v的简单路径上,有多少种颜色
思路:
- 树上问题转序列问题
- 首先获取欧拉序,即dfs序,不过回溯的时候也要加进序列,每个点出现两次
- 对于每个点给一个
- 假设
- 如果
lca(x,y)=x那么路径出现的点数为 出现一次的点 - 否则为 出现一次的点,加上lca
- 如果
cpp
int be[M],block,tot;
vector<int> adj[M];
int w[M],cnt[M];
int first[M],last[M],seq[M],top,dep[M],par[M][20];
bool st[M];
struct Q{
int id,l,r,p;
};
bool cmp(Q &x,Q &y){
return (be[x.l]^be[y.l])?be[x.l]<be[y.l]:((be[x.l]&1)?x.r<y.r:x.r>y.r);
}
void dfs(int u,int fa){
seq[++top]=u;
first[u]=top;
dep[u]=dep[fa]+1;
par[u][0]=fa;
for(int i=1;i<=__lg(dep[u]);i++)par[u][i]=par[par[u][i-1]][i-1];
for(auto v:adj[u]){
if(v==fa)continue;
dfs(v,u);
}
seq[++top]=u;
last[u]=top;
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
while(dep[u]>dep[v])u=par[u][__lg(dep[u]-dep[v])];
if(u==v)return u;
for(int i=__lg(dep[u]);i>=0;i--){
if(par[u][i]!=par[v][i])u=par[u][i],v=par[v][i];
}
return par[u][0];
}
void add(int x,int &res){
st[x]^=1;
if(!st[x]){
if(!(--cnt[w[x]]))res--;
}
else{
if(!(cnt[w[x]]++))res++;
}
}
void solve(){
int n,m;
cin>>n>>m;
vector<int> nums;
for(int i=1;i<=n;i++)cin>>w[i],nums.push_back(w[i]);
sort(nums.begin(),nums.end());
nums.erase(unique(nums.begin(),nums.end()),nums.end());
for(int i=1;i<=n;i++)w[i]=lower_bound(nums.begin(),nums.end(),w[i])-nums.begin();
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
adj[u].push_back(v);
adj[v].push_back(u);
}
vector<int> ans(m);
dfs(1,0);
vector<Q> q;
for(int i=0;i<m;i++){
int u,v;
cin>>u>>v;
if(first[u]>first[v])swap(u,v);
int p=lca(u,v);
if(p==u)q.push_back({i,first[u],first[v],0});
else q.push_back({i,last[u],first[v],p});
}
block=sqrt(1.0*top*top/m),tot=(top+block-1)/block;
for(int i=1;i<=tot;i++){
int l=(i-1)*block+1,r=min(top,i*block);
for(int j=l;j<=r;j++)be[j]=i;
}
sort(q.begin(),q.end(),cmp);
int res=0;
for(int i=0,l=1,r=0;i<m;i++){
auto [id,ql,qr,p]=q[i];
while(r<qr)add(seq[++r],res);
while(r>qr)add(seq[r--],res);
while(l<ql)add(seq[l++],res);
while(l>ql)add(seq[--l],res);
if(p)add(p,res);
ans[id]=res;
if(p)add(p,res);
}
for(int i=0;i<m;i++)cout<<ans[i]<<"\n";
}P4887 【模板】莫队二次离线(第十四分块(前体))
题意:
询问区间内最多有几对数,异或起来恰好有k个1
思路:
- 莫队离线移动为
- 数值最多为 ,即最多有14个1,可以暴力枚举
- 定义 表示
- 定义 表示
- 可以暴力计算
- 原区间为
- 假设 ,那么假设前缀和 表示 ,那么 ,对于区间 ,这个是不能直接算的需要离线下来算,即二次离线
- 同理,有以下
- 对于离线下来的range暴力求,本质上是移动l,r指针的贡献,为
- 最后再进行累加,因为求的是相对变化
cpp
int be[M],block,tot;
struct Q{
int id,l,r,res;
};
struct Range{
int i,l,r,p;
};
bool cmp(Q &x,Q &y){
return (be[x.l]^be[y.l])?be[x.l]<be[y.l]:((be[x.l]&1)?x.r<y.r:x.r>y.r);
}
void solve(){
int n,m,k;
cin>>n>>m>>k;
vector<int> w(n+10),f(n+10),g(2e4);
for(int i=1;i<=n;i++)cin>>w[i];
vector<Q> q;
for(int i=0;i<m;i++){
int l,r;
cin>>l>>r;
q.push_back({i,l,r,0});
}
if(k>14){
for(int i=0;i<m;i++)cout<<"0\n";
return;
}
block=sqrt(n),tot=(n+block-1)/block;
for(int i=1;i<=tot;i++){
int l=(i-1)*block+1,r=min(n,i*block);
for(int j=l;j<=r;j++)be[j]=i;
}
sort(q.begin(),q.end(),cmp);
vector<int> nums;
for(int i=0;i<1<<14;i++){
if(__builtin_popcount(i)==k)nums.push_back(i);
}
for(int i=1;i<=n;i++){
for(auto it:nums)g[w[i]^it]++;
f[i]=g[w[i+1]];
}
vector<Range> range[n+1];
for(int i=0,l=1,r=0;i<m;i++){
auto &[id,ql,qr,res]=q[i];
if(r<qr)range[l-1].push_back({i,r+1,qr,-1});
while(r<qr)res+=f[r++];
if(r>qr)range[l-1].push_back({i,qr+1,r,1});
while(r>qr)res-=f[--r];
if(l<ql)range[r].push_back({i,l,ql-1,-1});
while(l<ql)res+=f[l-1]+!k,l++;
if(l>ql)range[r].push_back({i,ql,l-1,1});
while(l>ql)res-=f[l-2]+!k,l--;
}
g.assign(2e4,0);
for(int i=1;i<=n;i++){
for(auto it:nums)g[w[i]^it]++;
for(auto [id,l,r,p]:range[i]){
for(int x=l;x<=r;x++)q[id].res+=g[w[x]]*p;
}
}
vector<int> ans(m);
for(int i=1;i<m;i++)q[i].res+=q[i-1].res;
for(int i=0;i<m;i++)ans[q[i].id]=q[i].res;
for(int i=0;i<m;i++)cout<<ans[i]<<"\n";
}