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