zoukankan      html  css  js  c++  java
  • 洛谷 P1890 【gcd区间】

    题意概括:

    给你一个长度为 (n) 的序列,以及 (m) 个询问,每次询问给出询问区间 (l) 以及 (r),试求 (lsim r) 之间所有数的 (gcd) 是多少。


    题解:

    这题方法真多啊(

    先总结一下几种已经出现了的方法。

    1.线段树or树状数组

    这俩是挺经典的求区间的工具,在这里就不多赘述,可以去看别的大佬的题解。

    2.dp

    对于每个区间可以用(O(n^2))的复杂度转移出来,然后询问就可以O(1)了,这个方法挺快,但是如果把 (n) 也提起来,就废了。

    3.分块

    这个我喜欢,分块是一种优雅的暴力,待会讲我的方法的时候会用到,大概就是把序列分成很多个块,然后每个块预处理一下,最后统计的时候整块算,大概复杂度是 (O(dfrac{n^2}{S}+mS)),S是块长。这里的 S 一般取 (sqrt{n})。但是在这么大的 (m) 面前,大概也是会被卡的。

    4.ST表

    待会也要用到,ST表是一种神奇的方法,不会的可以看 这里,他不仅能处理max/min,甚至可以处理一切有结合律的运算。

    然后就是重头戏了:

    5.lxl 的 ST 表

    听起来好高级的亚子,毒瘤劝退

    这种方法也就是我这题真正要讲的方法,就是分块套ST表,可以在保证空间为线性时,用期望 (O(n+m)) 的复杂度内求出 ST表 能求出的东西。虽然实际是可以卡到 (O(n+msqrt{n})) 的,但是在用的时候其实跑的挺快,下面步入正题。

    我们发现,其实ST表的空间是很大的,比如在1e7这样的数据面前,ST表基本就没了。所以我们考虑优化ST的空间。我们讲序列分成一块一块的,每次只预处理出每个整块的ST,这样我们的空间就降到了 (O(sqrt{n}logsqrt{n})),时间也是一样的。之后对于每个块,我们都可以直接取出答案,复杂度为 (O(1))

    其实到这里就可以开始写了,但是我们考虑一个问题,就是两边不完整的块该怎么办。如果暴力求,那么时间复杂度和普通分块是没有两样的,于是我们可以预处理出 每一块前缀gcd后缀gcd,可以参考下面这张图来理解:

    每个不同的色块表示这个点代表预处理的gcd后缀的区间,前缀也是一样的,这样一来,我们就可以预先用 (O(n)) 的复杂度预处理出这些前后缀,再 (O(1)) 查询就OK了。

    这个方法可以通过数据随机且 (n=2e7,m=2e7,TL=5.00s,ML=500MB)的题目,也就是这题

    上代码,上面没看懂的可以看程序:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=1005;
    const int maxm=1e6+5;
    const int block=50;
    int n,m;int p;
    inline int gcd(int a,int b)//gcd不解释
    {
        while(b^=a^=b^=a%=b);
        return a;
    }
    int st[block][20],belong[maxn],a[maxn];//ST是ST表,belong是每个点属于的块,a是原序列
    int pre[maxn],bk[maxn];//每块的前缀gcd和后缀gcd
    int lg[maxn];//预处理log2,用于ST表
    inline void init()//ST表的预处理,唯一和普通ST表不一样的地方就是它是到n我是到belong[n],也就是sqrtn
    {
        for(register int i=1;i<=n;i++)st[belong[i]][0]=a[i];
        for(register int i=1;(1<<i)<=p;i++)
        {
            for(register int j=1;j+(1<<i)-1<=p;j++)
            {
                st[j][i]=gcd(st[j][i-1],st[j+(1<<(i-1))][i-1]);
            }
        }
    }
    inline int query(int l,int r)//询问
    {
        int ans=0;
        if(belong[l]==belong[r]){for(int i=l;i<=r;i++)ans=gcd(ans,a[i]);return ans;}//如果在同一个块中,直接暴力找,因为这一块不好预处理,所以这也就是这题为什么会被卡到sqrtn的原因。
        if(belong[r]-belong[l]==1){return gcd(pre[r],bk[l]);}//如果块相差为1,也就是说这个询问的区间可以分为你右端点属于块的前缀,以及左端点属于的块的后缀,可以自己画图理解一下。
        else
        {
            int x=lg[belong[r]-belong[l]-1];
            ans=gcd(st[belong[l]-1][x],st[belong[r]-(1<<x)][x]);
            ans=gcd(ans,gcd(pre[r],bk[l]));
            return ans;
        }//ST表和前后缀的gcd查询,应该是ST表的基本操作。这里唯一不同的就是把原询问的区间的gcd变成了原询问块的gcd。
    }
    int main()
    {
        //听说从主函数开始阅读程序是个好习惯(?)
        int size=50;scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)belong[i]=(i-1)/size+1;
        lg[0]=-1;p=belong[n];
        for(register int i=1;i<=p;i++)lg[i]=lg[i>>1]+1;//处理log
        for(int i=1;i<=n;i++)pre[i]=(i%size^1)?gcd(pre[i-1],a[i]):a[i];//处理前缀,这里看不懂的话可以试着把它写成if,else的形式。
        for(int i=n;i>=1;i--)bk[i]=(i%size)?gcd(bk[i+1],a[i]):a[i];//后缀,和前缀差不多。
        init();//ST表预处理
        //前面是一大堆的预处理
        for(int i=1;i<=m;i++)
        {
            int l,r;scanf("%d %d",&l,&r);
            printf("%d
    ",query(l,r));
        }//询问,输出,结束。
    }
    

    似乎这种方法的延伸性很强,各位大佬们可以下去自己尝试尝试其他的询问区间的题目。

  • 相关阅读:
    Qt中的SIGNAL和SLOT
    Android单个模块编译
    decoupling of objetctoriented systems
    设计模式之Objectifier
    代码示例:调用SPS提供的remoting服务,在线把Office文档转换成html文档
    利用WSS做后台存储设计一个统一的信息发布平台
    元数据(metadata)在企业应用开发中的作用
    面向对象的软件设计中应当遵守的原则
    使用NUnit在.Net编程中进行单元测试
    最近在使用sps类库过程中发现了一个让我比较疑惑的问题(有关items属性的)
  • 原文地址:https://www.cnblogs.com/bovine-kebi/p/13369151.html
Copyright © 2011-2022 走看看