[线段树][区间lcm]强制在线带修区间LCM

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;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值