莫队算法是一种解决区间问题的算法,是一种很优的暴力算法。
莫队算法的基本思想是,知道一个区间[l,r]的一些信息,就可以在很短的时间内求出它的扩展区间的一些信息。
算法讲解挺详细的链接点击跳转到百度文库pdf文档
题目描述
Given a sequence of integers a1, a2, ..., an and q pairs of integers (l1, r1), (l2, r2), ..., (lq, rq), find count(l1, r1), count(l2, r2), ..., count(lq, rq) where count(i, j) is the number of different integers among a1, a2, ..., ai, aj, aj + 1, ..., an.
输入描述:
The input consists of several test cases and is terminated by end-of-file.
The first line of each test cases contains two integers n and q.
The second line contains n integers a1, a2, ..., an.
The i-th of the following q lines contains two integers li and ri.
输出描述:
For each test case, print q integers which denote the result.
示例1
输入
复制
3 2
1 2 1
1 2
1 3
4 1
1 2 3 4
1 3
输出
复制
2
1
3
备注:
* 1 ≤ n, q ≤ 105
* 1 ≤ ai ≤ n
* 1 ≤ li, ri ≤ n
* The number of test cases does not exceed 10.
题目意思是给定一个序列,输入下标(i,j)计算序列不包括区间(i,j)之后的不同数字的个数。
首先用暴力求解:直接对于每一个样例进行去掉中间区间再进行遍历
#include<iostream>
using namespace std;
const int maxn=100005;
int q,m;
int s[maxn],rec[maxn],Ans[manx];
struct Par{int lef,rig}par[maxn];
int main()
{
int i,j;
while(scanf("%d%d",&q,&m)!=EOF)
{
memset(Ans,0,sizeof(Ans));
for(i=0;i<q;i++)
{
scanf("%d",&s[i]);
}
for(i=0;i<m;i++)
{
scanf("%d%d",&par[i].lef,&par[i].rig);
}
for(i=0;i<m;i++)
{
memset(rec,0,sizoef(rec));
for(j=0;j<par[i].lef;j++)
{
if(rec[j]==0)
Ans[i]++;
rec[j]=1;
}
for(j=par[i].rig;j++)
{
if(rec[j]==0)
Ans[i]++;
rec[j]=1;
}
}
for(j=0;j<m;j++)
printf("%d\n",Ans[j]);
}
return 0;
}
提交的结果是运行超时。
莫队算法,同样是暴力但是它是根据知道了区间O(1)的时间去求它旁边的区间。
其算法思路是这样的,先将n个元素的序列分成sqrt(n)组,然后对查询区间先根据左端点所在的组排序,然后再根据右端点排序,再进行查询,每一次查询都使用前面得到的查询结果进行更新。
下面代码的思路是:先对整个序列进项查询,记录不同数组的总数,然后减去莫队算法得到的中间区间的总数,但是提交还是超时的,不过可以看出时间复杂度要比之前的暴力要更少。
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=100005;
int q,m;
int s[maxn],rec[maxn],md[maxn],Ans[maxn];
struct Par{int lef,rig,id;}par[maxn];
bool cmp(Par a,Par b)
{
if(md[a.lef]!=md[b.lef])return a.lef<b.lef;
return a.rig<b.rig;
}
void solve()
{
int i,lef=1,rig=1,ans=0;
for(i=1;i<=m;i++)
{
while(lef<par[i].lef)
if(--rec[s[lef++]]==0)
ans--;
while(lef>par[i].lef)
if(rec[s[--lef]]++==0)
ans++;
while(rig<par[i].rig)
if(rec[s[++rig]]++==0)
ans++;
while(rig>par[i].rig)
if(--rec[s[rig--]]==0)
ans--;
Ans[par[i].id]=ans;
}
return ;
}
int main()
{
int i,temp,total;
while(scanf("%d%d",&q,&m)!=EOF)
{
memset(rec,0,sizeof(rec));
temp=sqrt(q);
total=0;
for(i=1;i<=q;i++)
{
scanf("%d",&s[i]);
md[i]=(i-1)/temp+1;
if(rec[s[i]]==0)
{
total++;
rec[s[i]]=1;
}
}
for(i=1;i<=m;i++)
{
scanf("%d%d",&par[i].lef,&par[i].rig);
par[i].lef++;
par[i].rig--;
par[i].id=i;
}
sort(par+1,par+m+1,cmp);
memset(rec,0,sizeof(rec));
solve();
for(i=1;i<=m;i++)printf("%d\n",total-Ans[i]);
}
return 0;
}
下面的代码是优化后的莫队排序,通过直接计算两边区间的总数得到答案。提交时通过的,但是运行的时间和时间限制很接近,所以莫队算法并不是解这道题的最优算法。
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=100005;
int q,m;
int s[maxn],rec[maxn],md[maxn],Ans[maxn];
struct Par{int lef,rig,id;}par[maxn];
bool cmp(Par a,Par b)
{
if(md[a.lef]!=md[b.lef])return a.lef<b.lef;
return a.rig<b.rig;
}
void solve()
{
int i,lef=0,rig=q+1,ans=0;
for(i=1;i<=m;i++)
{
while(rig<par[i].rig)
{
rec[s[rig]]--;
if(rec[s[rig]]==0)
ans--;
rig++;
}
while(rig>par[i].rig)
{
rig--;
if(rec[s[rig]]==0)
ans++;
rec[s[rig]]++;
}
while(lef<par[i].lef)
{
lef++;
if(rec[s[lef]]==0)
ans++;
rec[s[lef]]++;
}
while(lef>par[i].lef)
{
rec[s[lef]]--;
if(rec[s[lef]]==0)
ans--;
lef--;
}
Ans[par[i].id]=ans;
}
return ;
}
int main()
{
int i,temp;
while(scanf("%d%d",&q,&m)!=EOF)
{
memset(rec,0,sizeof(rec));
memset(Ans,0,sizeof(Ans));
temp=sqrt(q);
for(i=1;i<=q;i++)
{
scanf("%d",&s[i]);
md[i]=(i-1)/temp+1;
}
for(i=1;i<=m;i++)
{
scanf("%d%d",&par[i].lef,&par[i].rig);
par[i].id=i;
}
sort(par+1,par+m+1,cmp);
solve();
for(i=1;i<=m;i++)printf("%d\n",Ans[i]);
}
return 0;
}