码迷,mamicode.com
首页 > 其他好文 > 详细

HDU 5381 The sum of gcd (2015年多校比赛第8场)

时间:2015-08-15 15:01:01      阅读:121      评论:0      收藏:0      [点我收藏+]

标签:莫队算法   区间gcd   

1.题目描述:点击打开链接

2.解题思路:本题利用莫队算法解决。由于是第一次学习这个算法,因此研究了比较长的一段时间才弄懂。首先,莫队算法解决的问题是无修改的离线区间查询问题。该算法实际上是由曼哈顿距离最小生成树演变来的,由于要处理m个区间,可以将这m个区间看做二维平面上的点,那么处理这m个区间就等价于让这m点连通,且总的转移代价最小。这其实就是一个曼哈顿距离最小生成树问题。


经典的曼哈顿距离最小生成树的时间复杂度是O(NlogN),莫队算法的时间复杂度是O(N^1.5)。不过本题还有一个地方,就是区间怎么处理。因为GCD相同的区间是可以合并的,因此不同的区间个数实际上是log级别的,一共有左右N个区间要处理,复杂度是O(NlogN)。下面用一个简单的例子来说明一下区间合并。

比如,数组是2,3,3,6,8,16,下标从1开始,下面的[i,j]->g表示a[i]到a[j]所有数到左端点的区间的GCD都是g。

a[1]=2: [1,1]->2, [2,6]->1

a[2]=3: [2,4]->3, [5,6]->1

a[3]=3: [3,4]->3, [5,6]->1

a[4]=6: [4,4]->4, [5,6]->2

a[5]=8: [5,6]->8

a[6]=16: [6,6]->16

从上面的例子中可以发现,以i为左端点构成的所有GCD值不等的区间实际上非常少。

处理完区间,接下来就是套用莫队算法,首先对M个查询区间进行分块排序,block=sqrt(n),首先对左端点进行排序,若相等,再对右端点排序。这样排序后可以保证总的转移代价是最小的,接下来就是进行区间转移了,举一个简单的例子来说明如何转移。

假如我们已经计算出了[L1,R1]的结果是res,下一个要计算的区间是[L2,R2],且L1<L2,R1<R2。

那么我们画图后发现,res需要减去从L1变化到L2-1的所有结果,同时要加上从R1变化到R2的所有结果。这样就得到了新的res。

3.代码:

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;

const int N=10000+5;
int n,m;
int block;
int a[N];
ll ans[N];

struct Node
{
    int L,R;
    int id;
    bool operator<(const Node&rhs)const
    {
        if(L/block==rhs.L/block)return R<rhs.R;
        return L/block<rhs.L/block;
    }
}q[N];

int gcd(int a,int b)
{
    return !b?a:gcd(b,a%b);
}

struct He
{
    int idx;
    ll g;
};

vector<He>vl[N],vr[N];

void preDeal()  //预处理所有以i为左端点的区间,他们的右端点都存放到vr[i]中;同理处理所有以i为右端点的区间,它们的左端点都存放到vl[i]中
{
    for(int i=1;i<=n;i++)//i从左到右变化,计算所有的vl[i](此时i作为一个固定的右端点)
    {
        if(i==1)vl[i].push_back(He{i,a[i]});//第一个区间,只有自身
        else
        {
            int curg=a[i];//当前的GCD值
            int L=i;      //第一个左端点
            for(auto&it:vl[i-1])//每次都以i-1为右端点的区间开始扩展,得到以i为右端点的区间
            {
                int g=gcd(it.g,curg);
                if(g!=curg)           //g!=curg说明以i-1为右端点的这个区间不能和加入a[i]后得到的新区间合并,那么单独保存新区间。若相等则试图继续扩展左端点L
                vl[i].push_back(He{L,curg});
                curg=g,L=it.idx;      //更新curg和L值,得到新的左端点L 和 以L为左端点,i为右端点的区间的GCD值
            }
            vl[i].push_back(He{L,curg});//最后一个区间
        }
    }
    for(int i=n;i>=1;i--)//用同样的方法处理以i为左端点的所有区间
    {
        if(i==n)vr[i].push_back(He{i,a[i]});
        else
        {
            int curg=a[i];
            int R=i;
            for(auto&it:vr[i+1])
            {
                int g=gcd(it.g,curg);
                if(g!=curg)
                vr[i].push_back(He{R,curg});
                curg=g,R=it.idx;
            }
            vr[i].push_back(He{R,curg});
        }
    }
}

ll calc(int type,int L,int R)//计算区间[L,R]的结果
{
    ll res=0;
    if(!type)
    {
        int tr=R;  //当前的右端点
        for(auto&it:vl[R])
        if(it.idx>=L)//如果当前区间的左端点>=L,则当前区间为[it.idx,tr]
        {
            res+=(tr-it.idx+1)*it.g;  //这里其实用到了GCD值的传递性:如果L<L1<R,gcd([L,R])=g1,gcd([L1,R])=g2(g2≥g1),那么必有gcd([L,L1-1])=g1
            tr=it.idx-1;   //更新右端点
        }
        else        //如果当前区间的左端点<L,则应该被算入的区间为[L,tr],由于它是最后一个区间了,因此break
        {
            res+=(tr-L+1)*it.g;
            break;
        }
    }
    else     //原理同上
    {
        int tl=L;
        for(auto&it:vr[L])
        if(it.idx<=R)
        {
            res+=(it.idx-tl+1)*it.g;
            tl=it.idx+1;
        }
        else
        {
            res+=(R-tl+1)*it.g;
            break;
        }
    }
    return res;
}
void solve()
{
    for(int i=1;i<=n;i++)
        vl[i].clear(),vr[i].clear();
    block=sqrt(n);//块数
    sort(q,q+m);

    preDeal();

    int L=1,R=0;从(1,0)点开始转移,此时res=0
    ll res=0;
    for(int i=0;i<m;i++)
    {
        while(R<q[i].R)
        {
            R++;//向右右端点,然后计算[L,R]区间的结果,累加给res
            res+=calc(0,L,R);
        }
        while(R>q[i].R)//向左移动右端点
        {
            res-=calc(0,L,R);
            R--;
        }
        while(L>q[i].L)
        {
            L--;
            res+=calc(1,L,R);
        }
        while(L<q[i].L)
        {
            res-=calc(1,L,R);
            L++;
        }
        ans[q[i].id]=res;//转移完毕。注意,res,L,R都不要清零
    }
    for(int i=0;i<m;i++)
        printf("%I64d\n",ans[i]);
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&q[i].L,&q[i].R);
            q[i].id=i;
        }
        solve();
    }
}



版权声明:本文为博主原创文章,未经博主允许不得转载。

HDU 5381 The sum of gcd (2015年多校比赛第8场)

标签:莫队算法   区间gcd   

原文地址:http://blog.csdn.net/u014800748/article/details/47680899

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!