洛谷 P4588 [TJOI2018]数学计算

传送门

Description

现在有一个数 \(x\),初始值为 \(1\)。有 \(Q\) 次操作,操作有两种:

\(1\) \(m\):将 \(x\) 变为 \(x \times m\),并输出 \(x \bmod M\)

\(2\) \(pos\):将 \(x\) 变为 \(x\) 除以第 \(pos\) 次操作所乘的数(保证第 \(pos\) 次操作一定为类型 \(1\),对于每一个类型 \(1\) 的操作至多会被除一次),并输出 \(x \bmod M\)

每个测试点共有 \(T\) 组输入。

Constraints

对于 \(20\%\) 的数据,\(1 \le Q \le 500\)

对于 \(100\%\) 的数据,\(1 \le Q \le 10^5\)\(T \le 5, M \le 10^9\)\(0 < m \leq 10^9\)

Solution 0 \(TLE\) or \(WA\)

扫一眼题目,这题似乎能用模拟解决。

然而我们发现,若使用高精度乘法,时间必然会不够,直接吃一个 TLE。

同时,由于出现了除法,所以在取模时应当想到逆元,但是 \(M\) 并不一定为质数,逆元也不可做。

Solution 1

注意看题目里我加粗的那行字,题中保证了每次乘操作在后面的除操作中至多只用一次。

想想后面的除操作,是不是就相当于将前面的乘操作抵消掉呢?(抵消掉,意思就是把乘操作的值变回 \(1\)

把“时间”(操作次数) \(i\) 抽象成一个个初始为 \(1\) 点,乘操作就是把该“时间” \(i\) 的值修改为乘上的值,除操作就是把这个位置上的值变为 \(1\),则每次询问的答案就是从 \(1\) 位置到 \(i\) 所有数之积对 \(M\) 取模。

可以看出,这就是单点修改和区间查询,自然能够想到使用树状数组或线段树维护。

Code

最近在学线段树,就拿线段树来写了~

注意一开始整棵线段树要全赋为 \(1\),注意开 long long。

// by youyou2007 in 2022.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define int long long
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
const int N = 1E5 + 5;
int MOD;
int T;
int q;
int m[N];
int f[N * 4]; 
void pushup(int k)
{
	f[k] = (f[lc] * f[rc]) % MOD; 
}
void modify(int l, int r, int q, int k, int d, int opt)//单点修改
{
	if(l == r && l == q)
	{
		if(opt == 1)
		{
			f[k] = d;
		}
		else
		{
			f[k] = 1;
		}
		return;
	}
	int mid = (l + r) / 2;
	if(q <= mid)
	{
		modify(l, mid, q, lc, d, opt);
	}
	else
	{
		modify(mid + 1, r, q, rc, d, opt);
	}
	pushup(k);
}
signed main()
{
	scanf("%lld", &T);
	while(T--)
	{
		scanf("%lld%lld", &q, &MOD);
		rep(i, 1, q * 4 + 5)//整棵线段树都要赋 1
		{
			f[i] = 1; 
		}
		rep(i, 1, q)
		{
			int opt, m;
			scanf("%lld%lld", &opt, &m);
			if(opt == 1)
			{
				modify(1, q, i, 1, m, 1);//如果是乘操作,就在位置 i 上面修改成 m
				printf("%lld\n", f[1] % MOD);//这里进行了一个简化,因为 i 位置之后都是 1,对答案无影响,所以可以直接输出整课线段树的积,即 f[1] 的值	
			}
			else
			{

				modify(1, q, m, 1, 1, 2);//如果是除操作,就在位置 m(pos)上面修改成 1
				printf("%lld\n", f[1] % MOD);
			}
		}
	}
	return 0;
}

posted @ 2022-06-26 21:54  panjx  阅读(57)  评论(0编辑  收藏  举报