数学计算(TJOI2018)-线段树分治

Description

小豆现在有一个数 x ,初始值为 1 。 小豆有 Q 次操作,操作有两种类型:
1 m: x=x*m ,输出 x mod M ;
2 pos: x=x/ 第 pos 次操作所乘的数(保证第 pos 次操作一定为类型 1,对于每一个类型 1 的操作至多会被除一次),输出 x mod M 。

Input

一共有 t 组输入。
对于每一组输入,第一行是两个数字 Q,M 。
接下来 Q 行,每一行为操作类型 op ,操作编号或所乘的数字 m (保证所有的输入都是合法的)。

Output

对于每一个操作,输出一行,包含操作执行后的 x mod M 的值

Sample Input

1
10 1000000000
1 2
2 1
1 2
1 10
2 3
2 4
1 6
1 7
1 12
2 7

Sample Output

2
1
2
20
10
1
6
42
504
84


wtcl
“首先mod不是质数,所以不能求逆元。”
因为忘了这句话而导致花了一小时思考为什么不能暴力。。。

  • 注意到有加入操作和删除操作,一个很典型的想法就是线段树分治。
  • 线段树分治:具有插入删除操作的信息影响的都是一个个时间区间,可以使用线段树来记录其覆盖的影响区间,并在其上通过标记的方式进行记录。

代码

#include <iostream>
#include <cstdio>
#define int long long
using namespace std;
const int maxn=100003;
int q,mod;
struct node{int l,r,mul;}a[maxn<<2];
struct node2{int lef,rig,val;}keep[maxn];
void build(int x,int left,int right)
{
	a[x].l=left,a[x].r=right,a[x].mul=1;
	if(left==right) return;
	int mid=left+right>>1;
	build(x<<1,left,mid),build(x<<1|1,mid+1,right);
}
void add(int x,int left,int right,int d)
{
	if(a[x].r<left||a[x].l>right) return;
	if(left<=a[x].l&&right>=a[x].r) return a[x].mul=a[x].mul*d%mod,void();
	add(x<<1,left,right,d),add(x<<1|1,left,right,d);
}
void dfs(int x)
{
	if(a[x].l==a[x].r) return cout<<a[x].mul<<'
',void();
	a[x<<1].mul=a[x<<1].mul*a[x].mul%mod,dfs(x<<1);
	a[x<<1|1].mul=a[x<<1|1].mul*a[x].mul%mod,dfs(x<<1|1);
}
signed main()
{
	freopen("tmp.in","r",stdin);
	int T; scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld%lld",&q,&mod);
		for(int i=1,opt,x;i<=q;++i)
		{
			keep[i]=(node2){0,0,0};
			scanf("%lld%lld",&opt,&x);
			if(opt==1) keep[i]=(node2){i,0,x};
			else keep[x].rig=i;
		}
		build(1,1,q);
		for(int i=1;i<=q;++i)
		{
			if(!keep[i].lef) continue;
			if(!keep[i].rig) add(1,keep[i].lef,q,keep[i].val);
			else add(1,keep[i].lef,keep[i].rig-1,keep[i].val);
		}
		dfs(1);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/wuwendongxi/p/14545878.html