Codeforces 1493D - GCD of an Array (数据结构)

Codeforces Round #705 (Div.2) D - GCD of an Array


题意

给定长度为 n n n的数组 { a } \{a\} { a},有 q q q次操作与询问

每次操作给定 i i i x x x,使得 a i = a i ∗ x a_i=a_i*x ai=aix

每次操作后询问此时这个数组所有元素的最大公因数GCD

限制

1 ≤ n ≤ 2 ⋅ 1 0 5 1\le n\le 2\cdot 10^5 1n2105

1 ≤ a i ≤ 2 ⋅ 1 0 5 1\le a_i\le 2\cdot 10^5 1ai2105

1 ≤ i ≤ n ,   1 ≤ x ≤ 2 ⋅ 1 0 5 1\le i\le n,\ 1\le x\le 2\cdot 10^5 1in, 1x2105




思路

(好像有点卡常的样子,也可能是后面多加了几句判断优化掉了)

如果觉得思路讲得有点乱的话可以直接参考代码,注释基本都写着


众所周知,对于准备求GCD的所有数字进行质因子分解

那么GCD的值就是每个质因子在所有数中出现的最小幂次的乘积

以这一储备知识作为前提,开始讨论解题方案


刚开始本来想的是使用线段树点修改来维护每个质因子 t t t在每个位置的出现次数

然后区间查询最小值来获取这个质因子目前的贡献 v v v

然后再开一个 p r e pre pre数组用于记录每个质因子最小幂次的前置状态

由于我们不能每次都算一遍每个质因子的贡献,所以只能尝试去维护答案变量 a n s ans ans

所以每次我们可以得到质因子 t t t所作出贡献的幂次差值为 v − p r e [ d ] v-pre[d] vpre[d]

故在当前点修改之后,需要将当前贡献加入答案,即让 a n s ans ans乘上 d v − p r e [ d ] d^{v-pre[d]} dvpre[d]

但明显的,线段树空间复杂度严格为 O ( 4 n ) O(4n) O(4n) 2 ⋅ 1 0 5 2\cdot10^5 2105以内存在 1 0 4 10^4 104以上个素数

换言之需要开 O ( 4 ⋅ 1 0 4 n ) O(4\cdot 10^4n) O(4104n)的空间来存线段树,显然不可行(存在大量空间冗余)

所以从空间角度进行优化


发现我们可以利用multiset的不查重以及自动排序的特点来维护最小值

故可以令 s t [ d ] st[d] st[d]容器来表示质因子 d d d数组 { a } \{a\} { a}中每个位置出现的幂次

为了使其不存在像线段树那样造成的空间冗余,故如果在某个位置质因子 d d d的幂次为 0 0 0,则不将其插入multiset中

使用multiset容器,只需要判断 s t [ d ] . s i z e ( ) st[d].size() st[d].size()是否等于 n n n就能得知是否在每个位置都有质因子 d d d存在

再通过 ∗ s t [ d ] . b e g i n ( ) *st[d].begin() st[d].begin()来获得最小值,再同上方法与 p r e [ d ] pre[d] pre[d]做差计算答案

这样就做到了类似线段树中“区间查询”的功能


然后考虑multiset容器怎么做到“单点修改”的功能

对于每个位置再引入一个map容器,使 m p [ i ] [ d ] mp[i][d] mp[i][d]用于表示质因子 d d d在位置 i i i的幂次

如果在操作过程中需要让 i i i位置的质因子 d d d的幂次加上 t t t

只需要先将原先表示的幂次 m p [ i ] [ d ] mp[i][d] mp[i][d] s t [ d ] st[d] st[d]中删去

再将现在表示的幂次 m p [ i ] [ d ] + t mp[i][d]+t mp[i][d]+t插入 s t [ d ] st[d] st[d]即可(记得操作后让 m p [ i ] [ d ] mp[i][d] mp[i][d]加上 t t t

这样便做到了“单点修改”的功能


综上便能做到利用multiset代替线段树并减少空间冗余(为 0 0 0则不分配空间原则)

最后注意优化常数即可




代码

(Pretests 872ms/2500ms)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll qpow(ll a,ll n){
    
    ll r=1;while(n){
    
    if(n&1)r=(r*a)%mod;n>>=1;a=(a*a)%mod;}return r;}

multiset<int> st[200050]; //记录每个因子在每个位置出现的次数,未出现则不需要插入
int pre[200050]; //附加在st上,用于记录上一状态某质因子出现的次数,与st首元素的差值可用于计算答案
map<int,int> mp[200050]; //记录每个位置每个质因子目前的幂次

vector<int> primvec; //素数
bool vis[200050];

void init() //素数筛,素数存入primvec内
{
    
    
    vis[0]=vis[1]=true;
    for(int i=2;i<=1000;i++)
    {
    
    
        if(!vis[i])
        {
    
    
            primvec.push_back(i);
            for(int j=i*i;j<=200000;j+=i)
                vis[j]=true;
        }
    }
    for(int i=1001;i<=200000;i++)
        if(!vis[i])
            primvec.push_back(i);
}

void solve()
{
    
    
    int n,q;
    cin>>n>>q;
    ll ans=1;
    
    for(int i=1;i<=n;i++)
    {
    
    
        int d;
        cin>>d;
        for(int j:primvec) //对于每个输入的数字,进行一次质因子分解
        {
    
    
            if(d==1) //直接跳出循环节省时间
                break;
            if(!vis[d]) //如果可以直接判断为素数,尽量直接跳出循环(重要优化)
            {
    
    
                mp[i][d]=1;
                st[d].insert(1);
                break;
            }
            
            int t=0;
            while(d%j==0) //分解质因子j,记录次数
            {
    
    
                d/=j;
                t++;
            }
            if(t)
            {
    
    
                mp[i][j]=t; //第i个位置的质因子j的幂次为t
                st[j].insert(t); //记录质因子j在每个位置出现的次数
            }
        }
    }
    
    for(int j:primvec)
    {
    
    
        if(st[j].size()==n) //如果质因子j在每个位置都出现至少一次
        {
    
    
            int tmp=*st[j].begin(); //此时最少的出现次数
            if(tmp!=pre[j]) //与pre做差,得到答案增幅
            {
    
    
                ans=ans*qpow(j,tmp-pre[j])%mod;
                pre[j]=tmp;
            }
        }
    }
    
    while(q--)
    {
    
    
        int p,d;
        cin>>p>>d;
        for(int j:primvec) //对d进行质因子分解
        {
    
    
            if(d==1) //此处优化同上
                break;
            if(!vis[d])
            {
    
    
                int &v=mp[p][d]; //直接引用以优化常数
                if(v) //如果mp[p][d]不为0,说明质因子d已经有幂次,需要将其先从st[d]中删去
                    st[d].erase(st[d].lower_bound(v));
                v++;
                st[d].insert(v);
                
                if(st[d].size()==n) //如果质因子j在每个位置都出现至少一次,下同
                {
    
    
                    int tmp=*st[d].begin();
                    if(tmp!=pre[d])
                    {
    
    
                        ans=ans*qpow(d,tmp-pre[d])%mod;
                        pre[d]=tmp;
                    }
                }
                break;
            }
            
            int t=0;
            while(d%j==0)
            {
    
    
                d/=j;
                t++;
            }
            if(t) //下同
            {
    
    
                int &v=mp[p][j];
                if(v)
                    st[j].erase(st[j].lower_bound(v));
                v+=t; //应增加t次
                st[j].insert(v);
                
                if(st[j].size()==n)
                {
    
    
                    int tmp=*st[j].begin();
                    if(tmp!=pre[j])
                    {
    
    
                        ans=ans*qpow(j,tmp-pre[j])%mod;
                        pre[j]=tmp;
                    }
                }
            }
        }
        cout<<ans<<'\n';
    }
}
int main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    init();
    solve();
    return 0;
}

https://www.cnblogs.com/stelayuri/p/14493186.html


猜你喜欢

转载自blog.csdn.net/qq_36394234/article/details/114464389