目录
题目大意
给你一个长度为 n n n的序列。 q q q次操作。每次操作 a i : = a i ∗ x a_i:=a_i*x ai:=ai∗x。每次操作完问你 g c d ( a 1 , . . . , a n ) gcd(a_1,...,a_n) gcd(a1,...,an).
n , q ≤ 2 e 5 n,q \leq 2e5 n,q≤2e5
题目思路
这题的关键点有:
1.gcd可以按素数独立计算贡献
2.所有数所产生的素数个数不会很多,在logn级别。
1.离线做法 + 线段树
分析:没有要求强制在线,所以我们可以考虑离线做。我们将初始化也看作是 n n n次操作。那么一开始序列就是全1序列。
做法:每次操作可以被描述为: ( 时 刻 , 位 置 , 相 乘 的 因 子 x ) (时刻,位置,相乘的因子x) (时刻,位置,相乘的因子x).然后我们对每个质因子单独考虑:
对这些数都质因分解.开 素 数 素数 素数个vector,里面就存上面的结构体。对时间排序.
接下来就是按每个质因子进行模拟。将乘法转化成加法。问题就变成了:单点加,查询整体最小值。直接线段树即可。每次查询完后,要与上一个修改时刻的最小值进行比较。如果没有变化,则代表没有贡献。所以开一个ans数组,来记录第 t i ti ti个时刻的贡献(类似打标记)。最后对其求一个前缀积即可。每次计算完一个质数的贡献后记得对线段树撤销所有修改.
时间复杂度:
O ( n n + n π ( a i ) l o g n ) O(n\sqrt{n}+n\pi(a_i)logn) O(nn+nπ(ai)logn).
其中 n n n\sqrt{n} nn来自于质因分解,
π ( a i ) \pi(a_i) π(ai)是 a i a_i ai的不同的质数个数. ≤ 6 \leq 6 ≤6.
l o g n logn logn为对线段树的操作的复杂度.
代码见代码仓库1-1
2.在线做法
2.1:动态开点线段树
分析:考虑一种暴力的方法:开 1 e 5 1e5 1e5个线段树。每次新增一个数之后,将其质因分解,将每个质数以及其指数大小插入到线段树中。维护一个 a n s ans ans.每次还是更新贡献。显然空间复杂度不够。
优化:我们利用动态开点线段树对空间进行复用即可。严格讲空间复杂度到 n l o g n l o g n nlognlogn nlognlogn.但是不难发现由于大小的限制和素数的限制远达不到上述上界。实测线段树空间开到 1 e 7 1e7 1e7即可.
时间复杂度: O ( n n + n π ( n ) l o g n ) O(n\sqrt{n}+n\pi(n)logn) O(nn+nπ(n)logn).
类似思路的题目:CF979D
代码见代码仓库2-1
2.2 暴力开multiset+map
分析:考虑一种暴力想法:对每个点开一个 1 e 5 1e5 1e5的 m a p map map. m p [ i ] [ j ] mp[i][j] mp[i][j]就代表第 i i i个位置因数 j j j的出现次数.然后对每个值开一个 m u l t i s e t multiset multiset. s e t [ i ] set[i] set[i]代表当前时刻所有含有值 i i i的数其指数大小.每次查询全局最小值是非常方便的.
之后暴力模拟即可。对 m u l t i s e t multiset multiset的操作就是先删除掉原来的值,然后再更改再插入。再查询最小值。
代码见代码仓库2-2(官方题解代码)
心得
这个题目收获还是挺多的。
1.get到动态开点线段树的一种用法.更进一步理解了线段树的本质。并且发现之前的板子有问题。更新了一种更好理解,空间更小的板子.
2.codeforces的题目还是不要想的太复杂了。很多时候先想怎么暴力的空间换时间。
3.单点修改+查询全局最值 优先考虑 s e t / m u l t i s e t set/multiset set/multiset
代码仓库
1.1:离线+线段树做法
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define vi vector<int>
#define vll vector<ll>
#define tl (t<<1)
#define tr (t<<1|1)
#define mid ((r + l) >> 1)
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
ll ksm (ll a , ll b){
ll ans = 1 , base = a;
while (b){
if (b & 1) ans = ans * base % mod;b >>= 1;base = base * base % mod;}return ans;}
struct Node
{
int ti , id , val;
};
int n , q;
vector<Node> a[maxn];
ll ans[maxn * 2];
const ll inf = 1e18;
int sum[maxn<<2];
void pushup(int t)
{
sum[t] = min(sum[tl] , sum[tr]);
}
void add(int l , int r , int t , int pos , int c)
{
if(l == r)
{
sum[t] += c;
return ;
}
if(pos <= mid)
add(l,mid,tl,pos,c);
else
add(mid+1,r,tr,pos,c);
pushup(t);
return ;
}
ll ask(int L,int R,int l,int r,int t)
{
if(L <= l && r <= R)
{
return sum[t];
}
ll ans=inf;
if(L <= mid)
ans = min(ans , ask(L,R,l,mid,tl));
if(R > mid)
ans = min(ans , ask(L,R,mid+1,r,tr));
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> q;
for (int i = 0 ; i <= n + q ; i++){
ans[i] = 1;
}
for (int i = 1 ; i <= n ; i++){
int x;cin >> x;
int g = x;
for (int j = 2 ; j * j <= g ; j++){
if (g % j) continue;
int cnt = 0;
while (g % j == 0){
g /= j;
cnt++;
}
a[j].pb({
i , i , cnt});
}
if (g != 1)
a[g].pb({
i , i , 1});
}
for (int i = n + 1 ; i <= n + q ; i++){
int id , x; cin >> id >> x;
int g = x;
for (int j = 2 ; j * j <= g ; j++){
if (g % j) continue;
int cnt = 0;
while (g % j == 0){
g /= j;
cnt++;
}
a[j].pb({
i , id , cnt});
}
if (g != 1)
a[g].pb({
i , id , 1});
}
for (int i = 2 ; i < maxn ; i++){
if (a[i].size() == 0) continue;
int last = 0;
for (auto & g : a[i]){
int ti = g.ti , id = g.id , val = g.val;
add(1 , n , 1 , id , val);
if (last == sum[1]) continue;
ans[ti] = (ans[ti] * ksm(i , sum[1] - last))%mod;
last = sum[1];
}
// 撤销贡献
for (auto & g : a[i]){
int id = g.id , val = g.val;
add(1 , n , 1 , id , -val);
}
}
for (int i = 1 ; i <= n + q ; i++){
ans[i] = ans[i - 1] * ans[i] % mod;
}
for (int i = n + 1 ; i <= n + q ; i++){
cout << ans[i] << endl;
}
return 0;
}
2.1:动态开点线段树做法
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define mid ((l + r) >> 1)
#define vi vector<int>
#define vll vector<ll>
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
ll ksm (ll a , ll b){
ll ans = 1 , base = a;
while (b){
if (b & 1) ans = ans * base % mod;b >>= 1;base = base * base % mod;}return ans;}
int n , q;
const int maxm = 1e7 + 2e5 + 5;
int ls[maxm] , rs[maxm] , sum[maxm] , rt[maxn] , tot;
void pushup (int t)
{
int tl = ls[t] , tr = rs[t];
sum[t] = min(sum[tl] , sum[tr]);
}
void add (int &t , int l , int r , int p , int c){
if (!t) t = ++tot;
if (l == r) {
sum[t] += c;
return ;
}
if (p <= mid) add(ls[t] , l , mid , p , c);
else add(rs[t] , mid + 1 , r , p , c);
pushup(t);
return ;
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> q;
ll ans = 1;
for (int i = 1 ; i <= n ; i++){
int x;cin >> x;
int g = x;
for (int j = 2 ; j * j <= g ; j++){
if (g % j) continue;
int cnt = 0;
while (g % j == 0){
g /= j;
cnt++;
}
ll f = sum[rt[j]];
add(rt[j] , 1 , n , i , cnt);
ans = (ans * ksm(j , sum[rt[j]] - f))%mod;
}
if (g != 1) {
ll f = sum[rt[g]];
add(rt[g] , 1 , n , i , 1);
ans = (ans * ksm(g , sum[rt[g]] - f))%mod;
}
}
for (int i = 1 ; i <= q ; i++){
int id , x;cin >> id >> x;
int g = x;
for (int j = 2 ; j * j <= g ; j++){
if (g % j) continue;
int cnt = 0;
while (g % j == 0){
g /= j;
cnt++;
}
ll f = sum[rt[j]];
add(rt[j] , 1 , n , id , cnt);
ans = (ans * ksm(j , sum[rt[j]] - f))%mod;
}
if (g != 1) {
ll f = sum[rt[g]];
add(rt[g] , 1 , n , id , 1);
ans = (ans * ksm(g , sum[rt[g]] - f))%mod;
}
cout << ans << endl;
}
return 0;
}
2.2:暴力开map+multiset做法
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int const maxn = 2e5 + 5, max_val = 2e5 + 5;
ll mod = 1e9 + 7, ans = 1;
int nxt[max_val], n;
multiset <int> cnt[max_val];
map <int, int> cnt_divisor[maxn];
void add(int i, int x) {
while (x != 1) {
int div = nxt[x], add = 0;
while (nxt[x] == div) add++, x = x / nxt[x];
int lst = cnt_divisor[i][div];
cnt_divisor[i][div] += add;
int lst_min = 0;
if ((int)cnt[div].size() == n) {
lst_min = (*cnt[div].begin());
}
if (lst != 0) {
cnt[div].erase(cnt[div].find(lst));
}
cnt[div].insert(lst + add);
if ((int)cnt[div].size() == n) {
for (int j = lst_min + 1; j <= (*cnt[div].begin()); ++j) {
ans = ans * (ll)div % mod;
}
}
}
}
main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
int q, l, x;
cin >> n >> q;
for (int i = 2; i < maxn; ++i) {
if (nxt[i] == 0) {
nxt[i] = i;
if (i > 10000) continue;
for (int j = i * i; j < maxn; j += i) {
if (nxt[j] == 0) nxt[j] = i;
}
}
}
for (int i = 1; i <= n; ++i) {
cin >> x;
add(i, x);
}
for (int i = 1; i <= q; ++i) {
cin >> l >> x;
add(l, x);
cout << ans << '\n';
}
return 0;
}