hdu 5381 2015多校第八场 莫队算法


还没学过莫队算法。。。。网上也找不到莫队算法的论文,只能勉强看着别人的代码打下来。。。


稍微介绍下莫队算法:

能使用莫队算法的前提是这样的--如果我们已知[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]);
    }
}

发布了50 篇原创文章 · 获赞 3 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章