Codeforces Round #705 (Div. 2) D. GCD of an Array (素因子分解+数据结构)

博客内容介绍了Codeforces Round #705 (Div. 2) D题的解决方案,该题要求对数组进行多次乘法操作并计算最大公约数(GCD)。文章提出通过素因子分解来优化计算,利用埃拉托色尼筛选法进行素因子分解,并使用动态维护的方法处理每次询问。此外,还探讨了使用线段树优化解法的可能性,通过线段树维护区间最大公约数及其素因子表达式,以提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:https://codeforces.com/contest/1493/problem/D

题目大意:输入两个数n,q(1<=n,q<=2*10^5),

接下来输入n个数a[1],a[2],a[3]....a[n](1<=a[i]<=2*10^5)

接下来q次询问。

每次询问输入i,x (1<=i<=n,1<=x<=2*10^5),我们需要把a[i]乘以xa[i]=a[i]*x,求出gcd(a[1],a[2],a[3]...a[n]),答案模以10^9+7

 

题解:由于数字可能很大,我们需要将数字进行素因子分解,可以用map存数字素因子分解的形式。

对于素因子分解,我们可以采用埃拉托色尼筛选法

对于每个素数,我们可以用mapmultiset维护素数的幂出现的次数。

由于每次都是乘操作,答案只增不降,对于每次询问,我们可以维护当前的答案ans,每次只更新变化的素因子的值。

复杂度:O(n*logn*k)k为素因子个数。

 代码如下:

#include<bits/stdc++.h>

using namespace std;
const int nn =210000;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 1000000007;
int n,q;
int a[nn];
bool isprime[nn];
vector<int> primeV;
vector<pair<int,int>> numPrime[nn];
map<int,int> aPrime[nn];
map<int,int> currentPrime[nn];
LL ans;
void calc(int v)
{
    int i=v;
    for(int j=0;j<int(primeV.size());j++)
    {
        int jnum = 0;
        while(v%primeV[j]==0)
        {
            v/=primeV[j];
            jnum++;
        }
        if(jnum > 0)
        {
            numPrime[i].push_back(make_pair(primeV[j],jnum));
        }
        if(v<=1)
            break;
    }
}
LL POW(LL x,LL y)
{
    LL ret=1;
    while(y)
    {
        if(y&1)
        {
            ret=(ret*x)%mod;
        }
        x=(x*x)%mod;
        y>>=1;
    }
    return ret;
}
void updatePrime(int prime,int beforev,int nowv)
{
    int beforefirst=currentPrime[prime].begin()->first;
    currentPrime[prime][beforev]--;
    if(currentPrime[prime][beforev]==0)
    {
        currentPrime[prime].erase(beforev);
    }
    currentPrime[prime][nowv]++;
    int nowfirst = currentPrime[prime].begin()->first;
    if(beforefirst!=nowfirst)
    {
        ans=(ans*POW(prime,nowfirst-beforefirst))%mod;
    }
}
void add(int i,int v)
{
    for(auto it=numPrime[v].begin();it!=numPrime[v].end();it++)
    {
        int beforev = aPrime[i][it->first];
        aPrime[i][it->first]+=it->second;
        updatePrime(it->first,beforev,aPrime[i][it->first]);
    }
}
int main()
{
    memset(isprime,true,sizeof(isprime));
    isprime[0]=isprime[1]=false;
    for(int i=2;i<=200000;i++)
    {
        if(isprime[i])
        {
            primeV.push_back(i);
            for(int j=i+i;j<=200000;j+=i)
            {
                isprime[j]=false;
            }
        }
    }
    for(int i=2;i<=200000;i++)
    {
        calc(i);
    }
    cin>>n>>q;
    for(int i=0;i<int(primeV.size());i++)
    {
        currentPrime[primeV[i]][0]=n;
    }
    ans=1;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        add(i,a[i]);
    }
    while(q--)
    {
        int i,x;
        scanf("%d%d",&i,&x);
        add(i,x);
        printf("%lld\n",ans);
    }
    return 0;
}

本题还可以用线段树来解决,我们用线段树维护区间的最大公约数,用unordered\_map来存最大公约数的素因子表达式。

对于每次更新,我们只需要更新x中出现的素数。

时间复杂度:O(n*logn*k)k为素因子个数。

线段树解决代码中的素因子分解方法更好:

首先求出每个数字的最小素因子nextPrime[i]

    memset(nextPrime,0,sizeof(nextPrime));
    nextPrime[0]=nextPrime[1]=1;
    for(int i=2;i<=200000;i++)
    {
        if(nextPrime[i]!=0)
            continue;
        nextPrime[i]=i;
        if(i>1000)
            continue;
        for(int j=i*i;j<=200000;j+=i)
        {
            if(nextPrime[j]==0)
                nextPrime[j]=i;
        }
    }

再利用nextPrime[i]循环求出数字的素因子:

void SplitePrime(int val)
{
    int id=val;
    while(val>1)
    {
        int num=0;
        int cur=nextPrime[val];
        while(cur==nextPrime[val])
        {
            val/=nextPrime[val];
            num++;
        }
        splitePrime[id].push_back(make_pair(cur,num));
    }
}

代码如下:

#include<bits/stdc++.h>

using namespace std;
const int nn =210000;
const int inff = 0x3fffffff;
const double eps = 1e-8;
typedef long long LL;
const double pi = acos(-1.0);
const LL mod = 1000000007;
int n,q;
int a[nn];
int treel[nn*20],treer[nn*20];
vector<pair<int,int>> splitePrime[nn];
int nextPrime[nn];
unordered_map<int,int> treev[nn*20];
int treeG[nn*20];
LL ans;
int GCD(int x,int y)
{
    if(y==0)
        return x;
    return GCD(y,x%y);
}
LL POW(LL x,LL y)
{
    LL ret=1;
    while(y)
    {
        if(y&1)
        {
            ret=(ret*x)%mod;
        }
        x=(x*x)%mod;
        y>>=1;
    }
    return ret;
}
void SplitePrime(int val)
{
    int id=val;
    while(val>1)
    {
        int num=0;
        int cur=nextPrime[val];
        while(cur==nextPrime[val])
        {
            val/=nextPrime[val];
            num++;
        }
        splitePrime[id].push_back(make_pair(cur,num));
    }
}
void add(int i,int val)
{
    for(auto it=splitePrime[val].begin();it!=splitePrime[val].end();it++)
    {
        int before = treev[i][it->first];
        treev[i][it->first]+=it->second;
        if(i==1 && before!=treev[i][it->first])
            ans=(ans*POW(it->first,treev[i][it->first]-before))%mod;
    }
    treeG[i]=val;
}
void pushup(int i)
{
    treeG[i]=GCD(treeG[2*i],treeG[2*i+1]);
    for(auto it=splitePrime[treeG[i]].begin();it!=splitePrime[treeG[i]].end();it++)
    {
        treev[i][it->first]=it->second;
        if(i==1)
            ans=(ans*POW(it->first,it->second))%mod;
    }
}
void build(int i,int l,int r)
{
    treel[i]=l;
    treer[i]=r;
    if(l==r)
    {
        add(i,a[l]);
        return;
    }
    int mid=(l+r)/2;
    build(2*i,l,mid);
    build(2*i+1,mid+1,r);
    pushup(i);
}
void update(int i,int id,int x)
{
    if(treel[i]==id&&treer[i]==id)
    {
        add(i,x);
        return;
    }
    int mid=(treel[i]+treer[i])/2;
    if(id<=mid)
        update(2*i,id,x);
    else
        update(2*i+1,id,x);
    for(auto it=splitePrime[x].begin();it!=splitePrime[x].end();it++)
    {
        int v = it->first;
        if(treev[2*i].find(v)!=treev[2*i].end() && treev[2*i+1].find(v)!=treev[2*i+1].end())
        {
            int before = treev[i][v];
            treev[i][v]=min(treev[2*i+1][v],treev[2*i][v]);
            if(i==1&& before!=treev[i][v])
                ans=(ans*POW(v,treev[i][v]-before))%mod;
        }
    }
}
int main()
{
    memset(nextPrime,0,sizeof(nextPrime));
    nextPrime[0]=nextPrime[1]=1;
    for(int i=2;i<=200000;i++)
    {
        if(nextPrime[i]!=0)
            continue;
        nextPrime[i]=i;
        if(i>1000)
            continue;
        for(int j=i*i;j<=200000;j+=i)
        {
            if(nextPrime[j]==0)
                nextPrime[j]=i;
        }
    }
    for(int i=2;i<=200000;i++)
    {
        SplitePrime(i);
    }
    cin>>n>>q;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    ans=1;
    build(1,1,n);
    while(q--)
    {
        int i,x;
        scanf("%d%d",&i,&x);
        update(1,i,x);
        printf("%lld\n",ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值