Codeforces 1493D - GCD of an Array (数据结构)
Codeforces Round #705 (Div.2) D - GCD of an Array
题意
给定长度为\(n\)的数组\(\{a\}\),有\(q\)次操作与询问
每次操作给定\(i\)与\(x\),使得\(a_i=a_i*x\)
每次操作后询问此时这个数组所有元素的最大公因数GCD是多少
限制
\(1\le n\le 2\cdot 10^5\)
\(1\le a_i\le 2\cdot 10^5\)
\(1\le i\le n,\ 1\le x\le 2\cdot 10^5\)
思路
(好像有点卡常的样子,也可能是后面多加了几句判断优化掉了)
如果觉得思路讲得有点乱的话可以直接参考代码,注释基本都写着
众所周知,对于准备求GCD的所有数字进行质因子分解
那么GCD的值就是每个质因子在所有数中出现的最小幂次的乘积
以这一储备知识作为前提,开始讨论解题方案
刚开始本来想的是使用线段树点修改来维护每个质因子\(t\)在每个位置的出现次数
然后区间查询最小值来获取这个质因子目前的贡献\(v\)
然后再开一个\(pre\)数组用于记录每个质因子最小幂次的前置状态
由于我们不能每次都算一遍每个质因子的贡献,所以只能尝试去维护答案变量\(ans\)
所以每次我们可以得到质因子\(t\)所作出贡献的幂次差值为\(v-pre[d]\)
故在当前点修改之后,需要将当前贡献加入答案,即让\(ans\)乘上\(d^{v-pre[d]}\)
但明显的,线段树空间复杂度严格为\(O(4n)\),\(2\cdot10^5\)以内存在\(10^4\)以上个素数
换言之需要开\(O(4\cdot 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<<'\n';
}
}
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