【雅礼2014】GCD&LCM

【问题描述】

给定一个由 N 个整数构成的数列 {A},
求出如下描述的两个最长子串:
可能会用

【输入数据】

对于每个测试点:
第一行包括一个整数 T,代表数据组数。
对于接下来的每一组数据,包括两行。
第一行,为一个整数 N 代表序列长度。
第二行,为用空格分隔的 N 个整数 Ai。

【输出数据】

对于第 i 组数据,你需要输出组数标示 “Case i: ” 其中 i 表示当前的数据组数。
紧接着,需要输出所要计算的参数α与β,以空格分隔。
如果不存在所要求的子串,对应的参数α或β设为 -1。

【输入样例 1】

3
2
7 2
4
2 2 3 4
3
2 2 4

【输出样例 1】

Case 1: 2 2
Case 2: 4 2
Case 3: -1 -1

【数据范围】

对于 20% 的数据, T = 1, N ⩽ 10, Ai ⩽ 10(1 ⩽ i ⩽ N)。
对于 50% 的数据, T ⩽ 10, N ⩽ 100, Ai ⩽ 100(1 ⩽ i ⩽ N)。
对于 70% 的数据, T ⩽ 20, N ⩽ 1000, Ai⩽ 1000(1 ⩽ i ⩽ N)。
对于 100% 的数据, 1⩽ T ⩽ 30, 1 ⩽ N ⩽ 10^5 , 1 ⩽ Ai ⩽ 10^6 (1 ⩽ i ⩽ N)。

【题目来源】

雅礼 2014 模拟赛 18
—————————————分割の线————————————————————

【分析】

对于每组数据我们将GCD和LCM分开讨论,

GCD部分:
因为最大公因数是多个数公共拥有的,所以(x,y,z)=((x,y),z)=((x,z),y)=((y,z),x)
易证,最大公因数的大小随着数字数目的增多而下降
如果有任意几个数的最大公因数为1,则所有数的最大公因数为1。反之,如果所有数的最大公因数不为1,则不存在任意几个数的最大公因数为1
伪代码表示为

    if(gcd(a1,a2,a3,...an)==1)printf("%d\n",n);
    else printf("-1");

LCM部分:
这部分的耗时相对来说要多的多。最小公倍数为这几个数的乘积,说明这几个数两两互质,即不存在相同的质因数。
这里有两种算法,两种都讲,但这里只代码实现第二种:

  1. 动态规划
    此处用f[i]表示以第i个数结尾的序列最长长度。
    f[i]=min(f[i-1]+1,i-k+1);k表示从左往右最后一个不与ai互质的数的序号。
    答案就是ans=max{f[1],f[2],f[3]…f[n]};
    时间复杂度为O(n);
  2. 队列维护
    构造并维护一个队列,使队列中的元素两两互质。
    从左到右依次进队,如果队列中的元素不互质,则队首元素出队,直至队列中元素变为互质。
    记录每次队列长度为si,ans=max{s1,s2,s3…sn};
    特别注意i < j,所以ans长度要大于1,如果ans==1,则要输出-1

如下是GCD队列维护的代码详解:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int T,n;//数据总数和母串长度 
int a[100100];//母串 
int flag[1000100];//筛法求质数 
int p[1000100],p_cnt=0;//统计质数
int fac[1000100];//fac[i]表示i的最小质数的序号,例如fac[i]==1,表示p[1]即2(方便分解质因数) 
int num[100100];//子串中所有质因数的序号及个数
void prepare()
{//欧拉线性筛 
    memset(flag,0,sizeof(flag));
    for(int i=2;i<=1e6;i++)
    {
        if(!flag[i])p[++p_cnt]=i,fac[i]=p_cnt;
        for(int j=1;j<=p_cnt;j++)
        {
            if(p[j]*i>1e6)break;
            flag[p[j]*i]=1;
            fac[p[j]*i]=j;
            if(i%p[j]==0) break;
        }
    }
    return ;
}
int gcd(int a,int b)
{//辗转相除法求最大公因数 
    if(b!=0) return gcd(b,a%b);
    return a;
}
void del(int x)
{//删除队首元素以及减去其所有质因数 
    while(x>1)
    {
        num[fac[x]]--;
        x/=p[fac[x]];
    }
    return;
}
bool check(int x)
{//检查队列中是否有元素和将要入队元素拥有一样的质因数
//如果有一样,即不互质,删除队首元素 
    while(x>1)
    {
        if(num[fac[x]]>0) return 0;
        x/=p[fac[x]];//直接除去x的最小质因数 
    }
    return 1;
}
void add(int x)
{//将元素压入队尾并加上其所有质因数 
    while(x>1)
    {
        num[fac[x]]++;
        x=x/p[fac[x]];
    }
    return ;
}
int read()
{//数据很大,强烈建议加读入优化 
    int x=0;
    char tmp=getchar();
    while(tmp<'0'||tmp>'9')tmp=getchar();
    while(tmp>='0'&&tmp<='9') x=(x<<3)+(x<<1)+tmp-'0',tmp=getchar();
    return x;
}
int main()
{
    freopen("gm.in","r",stdin);
    freopen("gm.out","w",stdout);
    prepare();
    T=read();
    for(int kase=1;kase<=T;kase++)
    {
        memset(num,0,sizeof(num));//队首为空时不含任何质因数 
        int Gcd,Lcm=1;//Lcm的长度至少为1,即元素自己 
        int n;
        n=read();
        for(int i=1;i<=n;i++)
            a[i]=read();
        Gcd=a[1];
        for(int i=2;i<=n;i++)
            Gcd=gcd(Gcd,a[i]);
        if(Gcd==1) Gcd=n;//只有母串的公因数为1,才有实际意义 
        else Gcd=-1;//否则输出-1 
        //以上是Gcd 
        int i=1,j=1;
        add(a[1]);//把a[1]及其所有质因数加入队列 
        while(j<n)
        {
            while(!check(a[j+1]))del(a[i]),i++;//保证入队元素和原来队列中的所有元素互质 
            add(a[j+1]);
            j++;//入队 
            Lcm=max(j-i+1,Lcm);//求Lcm的最大长度 
        }
        if(Lcm==1) Lcm=-1;//长度为1,不算子串 
        //以上是Lcm 
        printf("Case %d: %d %d\n",kase,Gcd,Lcm);
    }
    return 0;
}

写的不好,如有纰漏,请在下方评论区指出。

猜你喜欢

转载自blog.csdn.net/xyc1719/article/details/80302170