还没学过莫队算法。。。。网上也找不到莫队算法的论文,只能勉强看着别人的代码打下来。。。
稍微介绍下莫队算法:
能使用莫队算法的前提是这样的--如果我们已知[l,r]的答案,能在O(1)时间得到[l + 1,r]的答案以及[l,r - 1]的答案,即可使用莫队算法。时间复杂度为O(n * sqrt(n))。如果求[l + 1,r]和[l,r - 1]要在O(logn)下完成,则时间复杂度是O(n * sqrt(n) * logn)。 PS:莫队算法一般处理离线无修改的区间询问
在转移复杂度是O(1)的情况下,已知[l,r]要求[l',r']的答案,那么我们可以在O(|l - l'| + |r - r'|)内求得。
莫队在论文中使用了二维曼哈顿距离最小生成树,而这个东西要用到区域划分法+梳妆数组or线段树维护(复杂度极值nlogn),并用kruskal在nlogn下求得,但是实现起来太麻烦了。。。
所以一般用一个比较简单的方法--分块。
将sqrt(n)区间内的点分在一块当中,分为sqrt(n)块,按区间排序。以左端点所在块内为第一关键字,右端点为第二关键字,进行排序,也就是以(pos[l],r)进行排序,然后复杂度就保证是O(nlogn)了。。。以下为证明。。。。
一、i与i+1在同一块内,r单调递增,所以r是O(n)的。由于有n^0.5块,所以这一部分时间复杂度是n^1.5。
二、i与i+1跨越一块,r最多变化n,由于有n^0.5块,所以这一部分时间复杂度是n^1.5
三、i与i+1在同一块内时变化不超过n^0.5,跨越一块也不会超过2*n^0.5,不妨看作是n^0.5。由于有n个数,所以时间复杂度是n^1.5
于是就变成了O(n^1.5)了
以下为代码:
试了一下,如果不加分块的话必定超时,加了之后就是187ms,吓cry。。。。
#include
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 100005;
int n,m,a[maxn],ppos[maxn];
long long ans[maxn];
int v1[maxn][33],v2[maxn][33],v3[maxn][33],v4[maxn][33];
int size1[maxn],size2[maxn];
struct Q{
int l,r,id;
bool operator <(const Q & b)const{
if(ppos[l] == ppos[b.l])return r < b.r;
return ppos[l] < ppos[b.l];
}
void read(int i){scanf("%d%d",&l,&r);id = i;}
}q[maxn];
int gcd(int a,int b){return !b ? a : gcd(b,a % b);}
int pool[50],pos[50],cnt;
void unique(int &cnt)
{
int id = 0,p = pos[0];
for(int i = 0;i < cnt;i++)
{
if(pool[i] != pool[id]){
pos[id] = p;id++;p = pos[i];pool[id] = pool[i];
}
}
pos[id] = p;
cnt = id + 1;
}
void init()
{
cnt = 0;
for(int i = n;i >= 1;i--)
{
for(int j = 0;j < cnt;j++)pool[j] = gcd(pool[j],a[i]);
pool[cnt] = a[i];pos[cnt++] = i;
unique(cnt);
for(int j = cnt - 1;j >= 0;j--)
{
v1[i][cnt - 1 - j] = pool[j];
v2[i][cnt - 1 - j] = pos[j];
}
size1[i] = cnt;
}
cnt = 0;
for(int i = 1;i <= n;i++)
{
for(int j = 0;j < cnt;j++)pool[j] = gcd(pool[j],a[i]);
pool[cnt] = a[i];pos[cnt++] = i;
unique(cnt);
for(int j = cnt - 1;j >= 0;j--)
{
v3[i][cnt - 1 - j] = pool[j];
v4[i][cnt - 1 - j] = pos[j];
}
size2[i] = cnt;
}
}
int l,r;
ll sum;
void add_l(int v)
{
int s = l,t = r;
ll temp = 0;
int last = s;
for(int i = 0;i < size1[l];i++){
if(v2[l][i] < s)continue;
else if(v2[l][i] > t)
temp += (t - last + 1) * 1LL * v1[l][i];
else {
temp += (v2[l][i] - last + 1) * 1LL * v1[l][i];
last = v2[l][i] + 1;
}
if(v2[l][i] >= t)break;
}
sum += v * temp;
}
void add_r(int v)
{
int s = l,t = r;
ll temp = 0;
int last = t;
for(int i = 0;i < size2[r];i++){
if(v4[r][i] > t)continue;
else if(v4[r][i] < s)
temp += (last - s + 1) * 1LL * v3[r][i];
else {
temp += (last - v4[r][i] + 1) * 1LL * v3[r][i];
last = v4[r][i] - 1;
}
if(v4[t][i] <= s)break;
}
sum += v * temp;
}
int main()
{
int T;
scanf("%d",&T);
for(int cas = 1;cas <= T;cas++)
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)scanf("%d",a + i);
for(int i = 1;i <= n;i++)
ppos[i] = (i - 1) / 100;
init();
scanf("%d",&m);
for(int i = 0;i < m;i++)q[i].read(i);
sort(q,q + m);
sum = 0,l = 1,r = 0;
for(int i = 0;i < m;i++)
{
if(r < q[i].r){
for(r = r + 1;r <= q[i].r;r++)
add_r(1);
r--;
}
if(r > q[i].r){
for(;r > q[i].r;r--)
add_r(-1);
}
if(l > q[i].l){
for(l = l - 1;l >= q[i].l;l--)
add_l(1);
l++;
}
if(l < q[i].l){
for(;l < q[i].l;l++)
add_l(-1);
}
ans[q[i].id] = sum;
}
for(int i = 0;i < m;i++)
printf("%I64d\n",ans[i]);
}
}