题目链接
题意:
给你一个初始值为1的数组,你可以对其进行两种操作:
1.乘上某个数
2.除以第i次(第i次操作一定是操作1)乘上的数(其实就是把第i个操作取消掉)
每次操作过后求这个数的值。
思路:
线段树维护乘积,操作1就更新第i个节点,操作2就将该节点的值更新为1,将线段树初始节点全部初始化为1,最后直接求线段树最根部节点的值即可。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
const int N=1e5+7;
int sum[N<<2],add[N<<2],n,mod;;//Sum求和,Add为懒惰标记
int a[N];//存原数组数据下标[1,n]
//PushUp函数更新节点信息(这里是求和):
void pushup(int p)
{
sum[p]=sum[p*2]*sum[p*2+1]%mod;
}
//Build函数建树:
void build(int l,int r,int p)//l,r表示当前节点区间,rt表示当前节点编号
{
if(l==r)//若到达叶节点
{
sum[p]=a[l];//储存数组值
return ;
}
int mid=(l+r)/2;//左右递归
build(l,mid,p*2);
build(mid+1,r,p*2+1);//更新信息
pushup(p);
add[p]=1;
}
//点修改:
void updatedian(int L,int c,int l,int r,int p)//L表示要修改的点,C表示要修改的值,l,r表示当前节点区间,rt表示当前节点编号
{
if(l==r)//到叶节点,修改
{
sum[p]=c;
return ;
}
int mid=(l+r)/2;
if(L<=mid)//根据条件判断往左子树调用还是往右
{
updatedian(L,c,l,mid,p*2);
}
else
{
updatedian(L,c,mid+1,r,p*2+1);
}
pushup(p);//子节点更新了,所以本节点也需要更新信息
}
//区间修改:
//修改时的下推标记函数
//下推标记函数:
void pushdown(int p,int ln,int rn)//ln,rn为左子树,右子树的数字数量。
{
if(add[p]!=1)//该点的add值不为0,说明子区间的Sum需要根据Add的值来调整
{
//下推标记
add[p*2]*=add[p];
add[p*2+1]*=add[p];
//修改子节点的Sum使之与对应的Add相对应
sum[p*2]*=add[p];
sum[p*2+1]*=add[p];
add[p]=1;//清除本节点标记
}
}
//区间查询函数:
int query(int L,int R,int l,int r,int p)//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
{
if(L<=l&&r<=R)//在区间内,直接返回
{
return sum[p];
}
int mid=(l+r)/2;//下推标记,否则Sum可能不正确
pushdown(p,mid-l+1,r-mid);
int ans=1;//累计答案
if(L<=mid)
{
ans*=query(L,R,l,mid,p*2)%mod;
}
if(R>mid)
{
ans*=query(L,R,mid+1,r,p*2+1)%mod;
}
return ans%mod;
}
signed main()
{
IOS;
int t;
cin>>t;
while(t--)
{
cin>>n>>mod;
for(int i=1;i<=n;i++)
{
a[i]=1;
}
build(1,n,1); //建树
for(int i=1;i<=n;i++)
{
int a,b;
cin>>a>>b;
if(a==1)
updatedian(i,b,1,n,1);
else
updatedian(b,1,1,n,1);
cout<<query(1,n,1,n,1)%mod<<endl;
}
}
return 0;
}