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=ai∗x
每次操作后询问此时这个数组所有元素的最大公因数GCD
限制
1 ≤ n ≤ 2 ⋅ 1 0 5 1\le n\le 2\cdot 10^5 1≤n≤2⋅105
1 ≤ a i ≤ 2 ⋅ 1 0 5 1\le a_i\le 2\cdot 10^5 1≤ai≤2⋅105
1 ≤ i ≤ n , 1 ≤ x ≤ 2 ⋅ 1 0 5 1\le i\le n,\ 1\le x\le 2\cdot 10^5 1≤i≤n, 1≤x≤2⋅105
思路
(好像有点卡常的样子,也可能是后面多加了几句判断优化掉了)
如果觉得思路讲得有点乱的话可以直接参考代码,注释基本都写着
众所周知,对于准备求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] v−pre[d]
故在当前点修改之后,需要将当前贡献加入答案,即让 a n s ans ans乘上 d v − p r e [ d ] d^{v-pre[d]} dv−pre[d]
但明显的,线段树空间复杂度严格为 O ( 4 n ) O(4n) O(4n), 2 ⋅ 1 0 5 2\cdot10^5 2⋅105以内存在 1 0 4 10^4 104以上个素数
换言之需要开 O ( 4 ⋅ 1 0 4 n ) O(4\cdot 10^4n) O(4⋅104n)的空间来存线段树,显然不可行(存在大量空间冗余)
所以从空间角度进行优化
发现我们可以利用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