P4588 [TJOI2018]数学计算 题解
题意简述
\(x\) 初值为 \(1\),要求支持以下操作:
操作 1:将 \(x\leftarrow x\cdot m\),输出 \(x \mod M\);
操作 2:将 \(x\leftarrow x \div k\),\(k\) 是第 \(m\) 次操作时乘上的数,输出 \(x\mod M\)。
分析
线段树好题!线段树好题!线段树好题!
做过这道题后,我真的对线段树有了更加深厚的了解。
看到这个题目其实很难想象可以使用线段树来做,但我们可以发现,如果我们按照时间轴,即 \(1\sim Q\) 建一棵线段树,第 \(1\) 个数即为 \(x\),一开始全部赋初值 \(1\)。
第 \(i\) 个操作为 1 时就单点修改 \(i\leftarrow m\),这样 push_up
时就可以将 \(x\leftarrow x \cdot m\)。
为 2 时可以单点修改 \(i\leftarrow 1\),这样 push_up
时,\(x\) 就相当于除以了 \(k\)(第 \(m\) 次操作乘上的数)。
每次查询,输出 tr[1].sum
即可。
这样一分析,就变成了线段树裸题了(甚至还简单,都不用区间查询)。
代码实现
#include <bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
int x=0,f=0;char ch=getchar();
while(!isdigit(ch))f^=!(ch^45),ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
inline void write(int x){
if(x<0)x=-x,putchar('-');
if(x>=10)write(x/10);
putchar(x%10+'0');
}
inline void writeln(int x){write(x);puts("");}
int T,Q,mod;
struct segment{
int l,r,sum;
}tr[100005<<2];
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
inline void push_up(int p){tr[p].sum=(tr[ls(p)].sum*tr[rs(p)].sum)%mod;}
void build(int p,int l,int r){
tr[p]={l,r,1};
if(l==r)return;
int mid=l+r>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
push_up(p);
}
void update(int p,int x,int k){
if(tr[p].l==tr[p].r){
tr[p].sum=k;
return;
}
int mid=tr[p].l+tr[p].r>>1;
if(x<=mid)update(ls(p),x,k);
if(mid<x)update(rs(p),x,k);
push_up(p);
}
signed main(){
T=read();
while(T--){
Q=read();mod=read();
memset(tr,0,sizeof tr);
build(1,1,Q);
for(int i=1;i<=Q;i++){
int p=read(),m=read();
if(p==1)update(1,i,m),writeln(tr[1].sum%mod);
else update(1,m,1),writeln(tr[1].sum%mod);
}
}
#ifndef ONLINE_JUDGE
system("pause");
#endif
return 0;
}