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

25 篇文章 0 订阅

Codeforces Round #705 (Div. 2)

D. GCD of an Array

题意:给出一个长度为n的数组,每次对其中一个元素进行乘法操作,然后询问整个数组的GCD。

首先肯定想到的是,对数组的元素进行质因数分解,然后每次更新只用更新单个元素的质因数表达式。同时维护所有元素共有质因数的最小次幂,以便求GCD。

之后需要考虑的式用什么数据结构来维护每个元素的质因数表达式和所有元素共有质因数的最小次幂。
1、对于维护每个元素的质因数表达式。如果用vector则不便于修改,方法之一是可以使用map建立质因数和其次幂之间的映射,每次修改和查询所需的时间复杂度为 O ( l o g n ) O(logn) O(logn)
2、对于维护所有元素共有质因数的最小次幂,一开始的想法是用一个数组来维护,但是数组只能维护历史最小值,并不能动态维护最小值。解决方法之一是使用multiset,用每个multiset维护一个质因数的所有次幂,当且仅当集合大小为n时,表明这个质因数是所有元素共有质因数,因为multiset自动维护集合中元素的有序性,所以集合第一个元素即为最小次幂,修改即是对集合的插入和删除,复杂度为 O ( l o g n ) O(logn) O(logn)
3、每次修改后都重新遍历所有集合求GCD的做法时间复杂度太大,大概是 O ( q l o g n + q n ) O(qlogn+qn) O(qlogn+qn),注意到当且仅当集合的大小为n时,该质因数对答案才有贡献,而且修改只会让更多的质因数作出更多的贡献,所以我们可以维护 a n s ans ans,每次修改时,对 a n s ans ans造成变化只可能是修改的那个元素,所以我们只需要记录修改前质因数的最小次幂和修改后质因数的最小次幂,两者做差 x x x,那么该质因数 p p p对答案的贡献为 p x p^x px。这样一来询问的时间复杂度大致为 O ( q l o g n ) O(qlogn) O(qlogn)

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int max_n=2e5+5;
const int mod=1e9+7; 
int prime[max_n];
int vis[max_n];
map<int,int> f[max_n];
multiset<int> e[max_n];
multiset<int>::iterator it;
set<int> vi;
int cnt=0;
int tot,q;
ll ans=1;
void init(void)
{
	for(int i=2;i<max_n;i++)
	{
		if(!vis[i])prime[cnt++]=i;
		for(int j=0;j<cnt&&i*prime[j]<max_n;j++)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
ll qpow(ll a,int n,int mod)
{
	ll ans=1;
	while(n)
	{
		if(n&1)ans=ans*a%mod;
		a=a*a%mod;
		n>>=1;
	}
	return ans;
}
void update(int x,int v,map<int,int> &f)
{
	int pre=0;
	if(e[x].size()==tot)pre=*(e[x].begin());
	it=e[x].find(f[x]);
	if(it!=e[x].end())e[x].erase(it);
	f[x]+=v;
	e[x].insert(f[x]);
	if(e[x].size()==tot)ans=ans*qpow((ll)x,*(e[x].begin())-pre,mod)%mod;	
}
void resolve(int n,map<int,int> &f)
{
	for(int i=0;i<cnt&&prime[i]*prime[i]<=n;i++)
	if(n%prime[i]==0){
		int time=0;
		int pre=0;
		while(n%prime[i]==0)n/=prime[i],time++;
		update(prime[i],time,f);
		if(n==1)break;
	}
	if(n!=1){
		update(n,1,f);
	}
}
int main(void)
{
	init();
	scanf("%d%d",&tot,&q);
	int x,index;
	for(int i=1;i<=tot;i++)
	{
		scanf("%d",&x);
		resolve(x,f[i]);
	}
	while(q--)
	{
		scanf("%d%d",&index,&x);
		resolve(x,f[index]);
		printf("%lld\n",ans);
	}
}
//2 2
//2 2
//1 2
//2 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值