Description
给出一个长度为 n 的序列 a,需要执行 m 次操作:
-1 x y:令 a[x]=y
- 2 l r:回答区间 [l,r] 的 LCM,更具体的,输出LCM(a[l],a[l+1],⋯,a[r−1],a[r])
LCM(x,y) 为 x 和 y 的最小公倍数,如LCM(12,15)=60
由于结果可能过大,你只需要输出答案对998244353 取模后的结果。
Input
第一行给出两个正整数 n,m≤10^5,分别表示序列的长度和操作的次数。
第二行给出 n 个正整数 a[i]≤100,用空格隔开,行末有空格。
后面 m 行每行给出三个数字 op x y 用来描述操作,其中 op∈{1,2},x,y≤109。因为本题强制在线,所以所有的参数都需要重新计算得到:
- 操作 1:x=(x+last)modn+1,y=(y+last)mod100+1
- 操作 2:l=(x+last)modn+1,r=(y+last)modn+1。如果 l>r 则交换他们。
last 为最后一次操作 2 的答案,初始时 last=0
Output
对于每次操作 2 输出一行表示答案
Sample Input 1
4 5 2 3 5 7 2 123 321 2 456 654 1 1111 1111 2 2222 2222 2 3333 3333
Sample Output 1
105 105 7 17
Hint
样例:
五次操作分别为:
- 2 2 4
- 2 2 4
- 1 1 17
- 2 4 4
- 2 1 1
题意: 给出一组数,同时有两种操作,一个是单点更新,一个询问区间最小公倍数。
分析: 看起来类似维护区间gcd,但实际上是不一样的,区间gcd的值一定小于等于区间中最大值,因此过程中不需要取模,而区间lcm可能会非常大,线段树维护过程中就需要取模,即每个节点的lcm都需要保存取模后的值,否则会超出long long范围,但是一取模就会出错,毕竟lcm(a, b)%c并不等于lcm(a%c, b%c)%c。正解为考虑两个数取lcm的含义,两个数lcm的本质就是分别质因子分解,之后取每个质因子的最大幂次,乘起来就是lcm,因为gcd就是取每个质因子的最小幂次,再用a*b/gcd就得到了lcm,即剩下来的最大幂次。由于数列中数字大小最多为100,最多能有25个质因子,对于每个质因子建线段树维护幂次的区间最大值即可,query时把区间内每个质因子的最大幂次相乘就是答案。
具体代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define mod 998244353
using namespace std;
//不像区间gcd,由于区间lcm可能会很大,且过程中不能取模,因此不能像区间gcd一样用普通做法解决
//两个数lcm的本质就是分别质因子分解,之后取每个质因子的最大幂次,乘起来就是lcm
//因为gcd就是取每个质因子的最小幂次,再用a*b/gcd就得到了lcm,即剩下来的最大幂次
//由于数列中数字大小最多为100,最多能有25个质数,对于每个质数建线段树维护幂次的区间最大值即可
//lcm(a, b)%c!=lcm(a%c, b%c)%c,例如a=7,b=9,c=5
int n, m, _max[25][400020], a[25][100005];
int prime[25] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97};
void push_up(int id, int cnt)
{
_max[cnt][id] = max(_max[cnt][id<<1], _max[cnt][id<<1|1]);
}
void build(int l, int r, int id, int cnt)
{
if(l == r)
{
_max[cnt][id] = a[cnt][l];
return;
}
int m = l+r>>1;
build(l, m, id<<1, cnt), build(m+1, r, id<<1|1, cnt);
push_up(id, cnt);
}
void update(int L, int C, int l, int r, int id, int cnt)
{
if(l == r)
{
_max[cnt][id] = C;
return;
}
int m = l+r>>1;
if(L <= m)
update(L, C, l, m, id<<1, cnt);
else
update(L, C, m+1, r, id<<1|1, cnt);
push_up(id, cnt);
}
int query(int L, int R, int l, int r, int id, int cnt)
{
if(l == L && r == R)
return _max[cnt][id];
int m = l+r>>1;
if(m >= R)
return query(L, R, l, m, id<<1, cnt);
else if(m+1 <= L)
return query(L, R, m+1, r, id<<1|1, cnt);
else
return max(query(L, m, l, m, id<<1, cnt), query(m+1, R, m+1, r, id<<1|1, cnt));
}
signed main()
{
cin >> n >> m;
int t;
for(int i = 1; i <= n; i++)
{
scanf("%d", &t);
for(int j = 0; j < 25; j++)
while(t%prime[j] == 0)
{
a[j][i]++;
t /= prime[j];
}
}
for(int i = 0; i < 25; i++)
build(1, n, 1, i);
int op, x, y;
int last = 0;//记录上一次输出答案
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d", &op, &x, &y);
if(op == 1)
{
int L = (x+last)%n+1, C = (y+last)%100+1;
for(int j = 0; j < 25; j++)
{
int c = 0;
while(C%prime[j] == 0)
{
c++;
C /= prime[j];
}
update(L, c, 1, n, 1, j);
}
}
else
{
int t1 = (x+last)%n+1, t2 = (y+last)%n+1;
int l = min(t1, t2), r = max(t1, t2);
long long ans = 1;
for(int j = 0; j < 25; j++)
{
int t = query(l, r, 1, n, 1, j);
ans = (ans*(long long)pow(prime[j], t))%mod;
}
last = ans;
printf("%lld\n", ans);
}
}
return 0;
}