莫队模板
//莫队算法主要处理离线问题,查询只给出L,R
//当[L,R]很容易向[L-1,R],[L+1,R],[L,R-1],[L,R+1]转移时可用莫队
//注意转移的时候先扩张再收缩,L先向右,L再向左,最后再收缩
//add就是当前区间添加某元素时要做的操作
//del就是当前区间删除某元素时要做的操作
//add,del函数写的时候都要注意结构顺序
struct node
{
int l,r,id;
}Q[maxn];
int pos[maxn];//保存所在块
bool cmp(const node &a,const node &b)
{
if(pos[a.l]==pos[b.l])
return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int a[maxn];
ll ans[maxn];//保存每个查询得答案
int L=0,R=0;//多组记得重置
ll Ans=0;//多组记得重置
void add(int x);
void del(int x);
int main()
{
scanf("%d%d%d",&n,&m,&k);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]=a[i]^a[i-1];
pos[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L+1>Q[i].l)
{
L--;
add(L);
}
while(L+1<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
莫队算法第一题
http://codeforces.com/contest/617/problem/E
题意就是给出n个数的序列和数字k,q次询问,每次询问给出[L,R],求这个区间内有多少个连续区间的异或和等于k。
由于我们知道
,
所以我们只要把原数组转为前缀异或和数组,问题就变为给定区间内有多少对i,j满足A[i]^A[j]=k.,所以add,del函数的写法就很明显了。
莫队第一题代码
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
typedef long long ll;
const int maxn = 2e6+5;
struct node
{
int l,r,id;
}Q[maxn];
int pos[maxn];
bool cmp(const node &a,const node &b)
{
if(pos[a.l]==pos[b.l])
return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int n,m,k;
int flag[maxn];//数字在当前区间出现次数
int a[maxn];
ll ans[maxn];
int L=0,R=0;
ll Ans=0;
void add(int x)
{
Ans+=flag[a[x]^k];//a^b=c那么a^c=b
flag[a[x]]++;//先统计答案再加,以免多算一次当前的x
}
void del(int x)
{
flag[a[x]]--;//先减再统计答案,以免多减当前的x
Ans-=flag[a[x]^k];//a^b=c那么a^c=b
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
a[i]=a[i]^a[i-1];//数组转为前缀异或和数组
pos[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
flag[0]=1;//没有数字的时后,表示前缀异或和为0
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L+1>Q[i].l)
{
L--;
add(L);
}
while(L+1<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
莫队算法第二题
BZOJ2038
题意就是给你一个n个数字的数字序列,Q次查询操作给一个[L,R],求从这个区间任取两个数而且这两个数相等的概率
我们可以统计这个区间每种数的个数sum[x],数字x对答案的贡献为x*(x-1),最后除以整个区间的方案数len*(len-1)就可以了,所以add函数的时候,一个新的x对答案的贡献就是加入x之前的sum[x],del函数的时候,减少的应该是删除x之后的sum[x],这样就结束了。
莫队第二题代码
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e5+5;
struct data
{
int l,r,id;
}Q[maxn];
int pos[maxn];
int a[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
long long ans[maxn];
long long ans2[maxn];
int flag[maxn];
int L=1,R=0;//由于第一个删除0的操作对答案有影响,所以可以直接L=1开始。
long long Ans=0;
long long gcd_(long long a,long long b)
{
return b==0?a:gcd_(b,a%b);
}
void add(int x)//先统计再加
{
Ans+=flag[a[x]];
flag[a[x]]++;
}
void del(int x)//先减再统计
{
flag[a[x]]--;
Ans-=flag[a[x]];
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L>Q[i].l)
{
L--;
add(L);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
ans2[Q[i].id]=(1LL*(Q[i].r-Q[i].l+1)*(Q[i].r-Q[i].l))/2;
long long tmp = gcd_(ans[Q[i].id],ans2[Q[i].id]);
ans[Q[i].id]/=tmp;
ans2[Q[i].id]/=tmp;
if(ans[Q[i].id]==0) ans2[Q[i].id]=1;
}
for(int i=1;i<=m;i++)
printf("%lld/%lld\n",ans[i],ans2[i]);
return 0;
}
莫队算法第三题
https://www.spoj.com/problems/DQUERY/en/
题意就是区间不同数的个数,模板题
莫队第三题代码
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e6+5;
struct data
{
int l,r,id;
}Q[maxn];
long long ans[maxn];
int pos[maxn];
int a[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int flag[maxn];
int L=1,R=0;
long long Ans=0;
void add(int x)
{
flag[a[x]]++;
if(flag[a[x]]==1) Ans++;
}
void del(int x)
{
flag[a[x]]--;
if(flag[a[x]]==0) Ans--;
}
int main()
{
int n,m;
scanf("%d",&n);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L>Q[i].l)
{
L--;
add(L);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
莫队算法第四题
http://codeforces.com/problemset/problem/86/D
题意就是每种数字x对答案的贡献是(x*x*出现次数),所以add函数和del函数就很明显了。
但是由于读入比较多,需要挂个简单的读入挂
莫队第四题代码
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e6+5;
int a[maxn];
long long sum[maxn];
int pos[maxn];
long long ans[maxn];
struct data
{
int l,r,id;
}Q[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int L=0,R=0;
long long Ans=0;
void add(int x)
{
sum[a[x]]++;
Ans+=(1LL*(2LL*sum[a[x]]-1)*a[x]);//算出简单的转移减少常数
}
void del(int x)
{
sum[a[x]]--;
Ans-=(1LL*(2LL*sum[a[x]]+1)*a[x]);
}
int read()
{
ll x=0;
char c=getchar();
while(c < '0' || c > '9')
{
c = getchar();
}
while(c >= '0' && c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return x;
}
int main()
{
int n,t;
scanf("%d%d",&n,&t);
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
a[i]=read();
pos[i]=i/sz;
}
for(int i=1;i<=t;i++)
{
Q[i].l=read();
Q[i].r=read();
Q[i].id=i;
}
sort(Q+1,Q+1+t,cmp);
for(int i=1;i<=t;i++)
{
while(L>Q[i].l)
{
L--;
add(L);
}
while(R<Q[i].r)
{
R++;
add(R);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=t;i++) printf("%I64d\n",ans[i]);
return 0;
}
莫队算法第五题
HDU5213
给你一个n个数的序列a,给你q个询问,每次询问给两个不相交的区间,求a[i]+a[j]=k的方案数
i属于第一个区间,j属于第二个区间。
对于这种两个区间内查询得问题,我们要看看能不能转化为一个区间之内的查询操作。
我们设
为
区间内选i,j,
的方案数。若两个区间为
则答案为
所以我们对这四个部分分别处理就可以了。这就是多区间询问转为莫队算法的一种套路。
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
int a[maxn];
int pos[maxn];
ll sum[maxn];
ll ans[maxn];
int n,m,k;
struct data
{
int l,r,id,belong;//belong是这个查询属于哪个查询,id是这个查询对答案贡献是+还是-。
}Q[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int L=0,R=0;
ll Ans=0;
void add(int x)
{
if(k>a[x]&&k-a[x]<n) Ans+=sum[k-a[x]];
sum[a[x]]++;
}
void del(int x)
{
sum[a[x]]--;
if(k>a[x]&&k-a[x]<n) Ans-=sum[k-a[x]];
}
int main()
{
while(scanf("%d%d",&n,&k)!=EOF)
{
L=0,R=0,Ans=0;
for(int i=1;i<=n;i++) sum[i]=0;
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
scanf("%d",&m);
for(int i=1;i<=m;i++) ans[i]=0;
int cnt=1;
for(int i=1;i<=m;i++)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
Q[cnt].l=l1,Q[cnt].r=r2,Q[cnt].id=1,Q[cnt++].belong=i;
Q[cnt].l=l1,Q[cnt].r=l2-1,Q[cnt].id=-1,Q[cnt++].belong=i;
Q[cnt].l=r1+1,Q[cnt].r=r2,Q[cnt].id=-1,Q[cnt++].belong=i;
Q[cnt].l=r1+1,Q[cnt].r=l2-1,Q[cnt].id=1,Q[cnt++].belong=i;
}
cnt--;
sort(Q+1,Q+1+cnt,cmp);
for(int i=1;i<=cnt;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L>Q[i].l)
{
L--;
add(L);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].belong]+=1LL*Ans*Q[i].id;
}
for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}
return 0;
}
莫队算法第六题
Problem 2226 信心题
给定一个含有n个数字的数列,每个数字都有一个值a[i](下标从1开始)。定义第i个数字和第j个数字间的距离dis(i,j)=abs(i-j)。
数据范围:
N<=10^5
Q<=10^4
1<=a[i]<=10^3
1<=l<=r<=n
注意到a[i]小于1000,所以我们如果我们可以得到每种a[i]最左出现位置和最优出现位置,然后1000个a[i]扫一遍就可以
所以我们莫队维护每种数字在原序列中的最左下标和最右下标就可以了,所以add函数和del函数的写法就很简单了。
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5+5;
int a[maxn];
int cnt1[maxn];//存左下标
int cnt2[maxn];//存右下标
int pos[maxn];
int ans[maxn];
vector<int> v[1005];
struct data
{
int l,r,id;
}Q[10005];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int L,R,Ans;
int main()
{
int n,m;
while(scanf("%d",&n)!=EOF)
{
L=0,R=0,Ans=0;
for(int i=1;i<=n;i++)
{
v[i].clear();
cnt1[i]=0;
cnt2[i]=-1;//这里的初始化与莫队有关,需要思考一下,
}
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
pos[i]=i/sz;
scanf("%d",&a[i]);
v[a[i]].push_back(i);
}
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
cnt2[a[R]]++;//区间向右扩,当前数的最右下标右移
}
while(L>Q[i].l)
{
L--;
cnt1[a[L]]--;//区间向左扩,当前数的最左下标左移
}
while(L<Q[i].l)
{
cnt1[a[L]]++;//区间向右压,当前数的最左下标右移
L++;
}
while(R>Q[i].r)
{
cnt2[a[R]]--;//区间向左压,当前数的最右下标左移
R--;
}
Ans=0;
for(int j=1;j<=1000;j++)
{
if(cnt2[j]<0) continue;
int tmp1=v[j][cnt2[j]];
int tmp2=v[j][cnt1[j]];
Ans=max(Ans,tmp1-tmp2);
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
return 0;
}
莫队算法第七题
HDU4638
题意就是给你一个长度为n的序列,每次查询给一个区间,问区间可被分成多少个连续的段
例如 1,2,4,5被分为 [1,2],[4,5]两段。
我们可以考虑添加一个数对答案的影响,添加一个数的时候,如果他左右的数字都存在,显然段数-1
若左右存在某一个,段数不变
若左右均不存在,则段数+1
删除一个数的时候类似,自己推导一下就可以了。
莫队第七题代码
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
#define dbg(x) cout<<#x<<" = "<<x<<endl;
const int maxn = 1e5+5;
int a[maxn];
int pos[maxn];
int ans[maxn];
int vis[maxn];
struct data
{
int l,r,id;
}Q[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
int L,R,Ans;
void add(int i)
{
int id=a[i];
Ans-=(vis[id-1]+vis[id+1]-1);
vis[id]=1;
}
void del(int i)
{
int id=a[i];
Ans+=(vis[id-1]+vis[id+1]-1);
vis[id]=0;
}
int main()
{
int n,m;
int T;
scanf("%d",&T);
while(T--)
{
L=1,R=0,Ans=0;
scanf("%d%d",&n,&m);
for(int i=0;i<=2*n;i++)
{
vis[i]=0;
}
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+m,cmp);
for(int i=1;i<=m;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L>Q[i].l)
{
L--;
add(L);
}
while(R>Q[i].r)
{
del(R);
R--;
}
while(L<Q[i].l)
{
del(L);
L++;
}
ans[Q[i].id]=Ans;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
return 0;
}
莫队算法第八题
HDU4676
题意就是求某个区间内两两gcd之和。
关于一个区间内两两GCD之和,我们有一个推导:
对一个序列的某个区间L,R,每个数两两之间的GCD之和为
d在这里指这个区间的所有约数
通过这个公式,我们就可以很方便的通过枚举新加进来的数字的因子进行add和del,推算一下就可以了。
这里每个数的因子和欧拉函数要与处理一下保证复杂度。
莫队第八题代码
#include<stdio.h>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
#define dbg(x) cout<<#x<<" = "<<x<<endl
const int maxn = 1e5+5;
struct data
{
int l,r,id;
}Q[maxn];
int pos[maxn];
int phi[maxn];
int pri[maxn];
int a[maxn];
int ans[maxn];
int Flag[maxn];
vector<int> v[maxn];
int num[maxn];
bool cmp(const data &a,const data &b)
{
if(pos[a.l]==pos[b.l]) return a.r<b.r;
return pos[a.l]<pos[b.l];
}
void Getphi(int Max)//欧拉函数与因子的预处理
{
phi[1] = 1;
for (int i = 2; i <= Max; i ++)
{
if (!Flag[i])
{
phi[i] = i - 1;
pri[++ pri[0]] = i;
}
for (int j = 1; j <= pri[0]; j ++)
{
if (1ll * i * pri[j] > Max) break;
Flag[i * pri[j]] = 1;
if (i % pri[j] == 0)
{
phi[i * pri[j]] = phi[i] * pri[j];
break;
}
phi[i * pri[j]] = phi[i] * (pri[j] - 1);
}
}
for(int i=1;i<=Max;i++)
{
for(int j=i;j<=Max;j+=i)
v[j].push_back(i);
}
}
int L=0,R=0;
long long Ans=0;
void add(int x)
{
for(int i=0;i<v[a[x]].size();i++)
Ans+=1LL*phi[v[a[x]][i]]*num[v[a[x]][i]];
for(int i=0;i<v[a[x]].size();i++)
num[v[a[x]][i]]++;
return ;
}
void del(int x)
{
for(int i=0;i<v[a[x]].size();i++)
num[v[a[x]][i]]--;
for(int i=0;i<v[a[x]].size();i++)
Ans-=1LL*phi[v[a[x]][i]]*num[v[a[x]][i]];
return ;
}
int main()
{
int n,q;
int t;
int cnt=1;
Getphi(20000);
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
L=0,R=0,Ans=0;
for(int i=1;i<=n;i++) num[i]=0;
int sz=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
pos[i]=i/sz;
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+1+q,cmp);
for(int i=1;i<=q;i++)
{
while(R<Q[i].r)
{
R++;
add(R);
}
while(L>Q[i].l)
{
L--;
add(L);
}
while(L<Q[i].l)
{
del(L);
L++;
}
while(R>Q[i].r)
{
del(R);
R--;
}
ans[Q[i].id]=Ans;
}
printf("Case #%d:\n",cnt++);
for(int i=1;i<=q;i++)
printf("%d\n",ans[i]);
}
return 0;
}
未完待续…