還沒學過莫隊算法。。。。網上也找不到莫隊算法的論文,只能勉強看着別人的代碼打下來。。。
稍微介紹下莫隊算法:
能使用莫隊算法的前提是這樣的--如果我們已知[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]);
}
}