题目链接: An easy problem
大致题意
现在有一个初始值 X = 1. 给定操作次数Q 和 取余数M. 操作有如下两种:
①输入1 y 表示给当前的X乘上一个y
②输入2 y 表示给当前X除以第y次操作的操作数(保证给定的第y次操作为操作1类型)(保证不会输入相同的两个y)
要求: 在每次操作后 输出 X % M;
解题思路
最开始乍一想, 直接用小数去模拟了, 然后发现, 如果当前的X比M大了, 那么取余后可能会是个很小的数字, 这样可能导致接下来的除法操作无法完成.
然后立马想到用大数去模拟.然后就被T了… 因为这样最坏会有1E15的复杂度…
此时冷静下来仔细思考, 这道题其实有他的特点: 即他每次除的数字都是之前出现过的, 且也只会除一次.
如果不考虑除法, 我可以搞个前缀乘来得到当前操作1后的答案. 那么对于除法, 显然除法不适用于取余, 但是因为这个除数的特殊性: 是之前出现的一个乘数, 那么我们立马想到, 我们不乘以那个数字不就可以了吗?
这时, 我们就应当立刻去想到用线段树维护前缀乘, 不乘某个数字相当于把那个数字改成1即可.
==>算法: 线段树 单点修改+区间查询.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
int n, m;
int num[N];
struct node {
int l, r;
ll val;
}t[N << 2];
void pushup(int x) { t[x].val = t[x << 1].val * t[x << 1 | 1].val % m; }
void build(int l, int r, int x = 1) {
if (l == r) { t[x] = { l, r, 1 }; return; }
t[x] = { l, r, 1 };
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
pushup(x);
}
void modify(int a, ll c, int x = 1) {
if (t[x].l == a && t[x].r == a) { t[x].val = c; return; }
int mid = t[x].l + t[x].r >> 1;
modify(a, c, x << 1 | (a > mid));
pushup(x);
}
ll ask(int l, int r, int x = 1) {
if (l <= t[x].l && r >= t[x].r) return t[x].val;
int mid = t[x].l + t[x].r >> 1;
ll res = 1;
if (l <= mid) res = res * ask(l, r, x << 1) % m;
if (r > mid) res = res * ask(l, r, x << 1 | 1) % m;
return res;
}
int main()
{
int tt; cin >> tt;
rep(T, tt) {
printf("Case #%d:\n", T);
scanf("%d %d", &n, &m);
build(1, n);
rep(i, n) {
int op, x; scanf("%d %d", &op, &x);
if (op == 1) modify(i, x);
else modify(x, 1);
printf("%lld\n", ask(1, n) % m);
}
}
return 0;
}