Codeforces Round #705 (Div.2) D - GCD of an Array
题意
给定长度为(n)的数组({a}),有(q)次操作与询问
每次操作给定(i)与(x),使得(a_i=a_i*x)
每次操作后询问此时这个数组所有元素的最大公因数GCD是多少
限制
(1le nle 2cdot 10^5)
(1le a_ile 2cdot 10^5)
(1le ile n, 1le xle 2cdot 10^5)
思路
(好像有点卡常的样子,也可能是后面多加了几句判断优化掉了)
如果觉得思路讲得有点乱的话可以直接参考代码,注释基本都写着
众所周知,对于准备求GCD的所有数字进行质因子分解
那么GCD的值就是每个质因子在所有数中出现的最小幂次的乘积
以这一储备知识作为前提,开始讨论解题方案
刚开始本来想的是使用线段树点修改来维护每个质因子(t)在每个位置的出现次数
然后区间查询最小值来获取这个质因子目前的贡献(v)
然后再开一个(pre)数组用于记录每个质因子最小幂次的前置状态
由于我们不能每次都算一遍每个质因子的贡献,所以只能尝试去维护答案变量(ans)
所以每次我们可以得到质因子(t)所作出贡献的幂次差值为(v-pre[d])
故在当前点修改之后,需要将当前贡献加入答案,即让(ans)乘上(d^{v-pre[d]})
但明显的,线段树空间复杂度严格为(O(4n)),(2cdot10^5)以内存在(10^4)以上个素数
换言之需要开(O(4cdot 10^4n))的空间来存线段树,显然不可行(存在大量空间冗余)
所以从空间角度进行优化
发现我们可以利用multiset的不查重以及自动排序的特点来维护最小值
故可以令(st[d])容器来表示质因子(d)在数组({a})中每个位置出现的幂次
为了使其不存在像线段树那样造成的空间冗余,故如果在某个位置质因子(d)的幂次为(0),则不将其插入multiset中
使用multiset容器,只需要判断(st[d].size())是否等于(n)就能得知是否在每个位置都有质因子(d)存在
再通过(*st[d].begin())来获得最小值,再同上方法与(pre[d])做差计算答案
这样就做到了类似线段树中“区间查询”的功能
然后考虑multiset容器怎么做到“单点修改”的功能
对于每个位置再引入一个map容器,使(mp[i][d])用于表示质因子(d)在位置(i)的幂次
如果在操作过程中需要让(i)位置的质因子(d)的幂次加上(t)
只需要先将原先表示的幂次(mp[i][d])从(st[d])中删去
再将现在表示的幂次(mp[i][d]+t)插入(st[d])中即可(记得操作后让(mp[i][d])加上(t))
这样便做到了“单点修改”的功能
综上便能做到利用multiset代替线段树并减少空间冗余(为(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<<'
';
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
init();
solve();
return 0;
}
https://blog.csdn.net/qq_36394234/article/details/114464389