SSL20211101可重集

T202677 可重集

题目

题目背景

stoorz 有一个可重集。

有一天他突然发现一个惊人的消息:这个可重集,它居然是可重的!!!

所以他出了这道题。

题目描述

有一个可重集,初始为空集。

n n n 个操作,每个操作均属于以下两种类型之一:

  • 1 x:往可重集里加入一个正整数 x x x
  • 2 x:在可重集中删除数字 x x x。如果数字 x x x 出现了多次只删除一次。如果可重集内没有数字 x x x,那就忽略这次操作。

m m m 次询问。每次询问给出两个数 l , r l,r l,r,你需要回答如果依次执行第 l l l 至第 r r r 个操作,最后可重集内的数的乘积 mod  p \text{mod } p mod p 的值。特别的,我们定义空集的乘积为 1 1 1

输入格式

第一行三个正整数 n , m , p n,m,p n,m,p,分别表示操作的数量,询问的次数,模数。

第二行至第 n + 1 n+1 n+1 行,每行两个正整数 o p t , x opt,x opt,x,表示这次操作的类型,以及操作的数 x x x

最后 m m m 行,每行两个正整数 l , r l,r l,r,表示一次询问。

输出格式

输出 m m m 行,其中第 i i i 行表示第 i i i 次询问的答案。

输入输出样例

输入 #1

7 4 37
1 5
1 7
1 7
1 6
2 7
2 5
1 8
1 7
2 5
4 7
2 6

输出 #1

3
5
11
5

输入 #2

见附件 example2.in
该样例满足 Subtask 2 的特殊限制。

输出 #2

见附件 example2.ans

说明/提示

样例解释:对于 4 4 4 次询问询问,操作后他们的可重集分别如下:

{ 7 , 6 , 8 } , { 7 , 6 } , { 6 , 8 } , { 7 , 6 } \{7,6,8\},\{7,6\},\{6,8\},\{7,6\} {7,6,8},{7,6},{6,8},{7,6}


本题采用捆绑测试。

子任务编号分值 n , m ≤ n,m\leq n,m特殊限制
1 1 1 20 p t s 20\rm pts 20pts 1 0 3 10^3 103
2 2 2 20 p t s 20\rm pts 20pts 3 × 1 0 5 3\times 10^5 3×105 p p p 是质数且所有操作的 x < p x<p x<p,所有询问的 l = 1 l=1 l=1
3 3 3 15 p t s 15\rm pts 15pts 3 × 1 0 5 3\times 10^5 3×105 p p p 是质数,所有询问的 l = 1 l=1 l=1
4 4 4 20 p t s 20\rm pts 20pts 3 × 1 0 5 3\times 10^5 3×105所有询问的 l = 1 l=1 l=1
5 5 5 25 p t s 25\rm pts 25pts 3 × 1 0 5 3\times 10^5 3×105

对于 100 % 100\% 100% 的数据, n , m ≤ 3 × 1 0 5 n,m\leq 3\times 10^5 n,m3×105 1 ≤ p ≤ 1 0 9 1\leq p\leq 10^9 1p109,每次操作满足 o p t ∈ { 1 , 2 } opt\in\{1,2\} opt{1,2} 1 ≤ x ≤ 1 0 9 1\leq x\leq 10^9 1x109,每次询问满足 1 ≤ l ≤ r ≤ n 1\leq l\leq r\leq n 1lrn

附件下载

example2.zip113.69KB

思路

对于原操作序列,每个删除操作对应的添加操作是一定的.

我们求出删除操作对应的添加操作,设操作 i i i对应操作 j j j( j < i , o p t i = 2 , o p t j = 1 j<i , opt_i=2 , opt_j=1 j<i,opti=2,optj=1).

设一个数组 a n a_n an(初始为1) , 询问按照右端点排序后从左向右扫描,同时,我们用指针 j j j遍历操作,若 o p t j = 1 opt_j=1 optj=1,则 a j = x j a_j=x_j aj=xj,若 o p t j = 2 opt_j=2 optj=2 j j j有对应的操作,则 a p r e j = 1 a_{pre_j}=1 aprej=1.

对于一个查询 l , r l,r l,r, ( ∏ k = l r a k )   m o d   p (\prod^r_{k=l}a_k) \bmod p (k=lrak)modp就是该查询的答案.

序列 a a a可用单点修改,区间查询的线段树维护.

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <stack>
#include <vector>

#define int long long
using namespace std;

using ll = long long;
int read() {
	int re = 0;
	char c = getchar();
	bool negt = false;
	while(c < '0' ||c > '9')
		negt |= (c == '-') , c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0'  , c = getchar();
	return negt ? -re : re;
}

const int N = 3e5 + 10 , M = 3e5 + 10;

struct Query {
	int l , r;
	int id;
} query[M];
bool cmp(Query a , Query b) {
	return a.r < b.r;
}

int n , m , mod;
int opt[N] , x[N];
int pre[N];
int ans[N];

unordered_map <int , int> mp;
vector <stack<int>* > stk;

struct SegmentTree {
	struct Node {
		int l , r;
		int ls , rs;
		ll dat;
		Node() {
			dat = 1;
		}
	} node[N * 4];
#define ls(_) node[_].ls
#define rs(_) node[_].rs
	int newnode() {
		static int cnt = 0;
		return ++cnt;
	}
	int build(int l , int r) {
		int id = newnode();
		node[id].l = l , node[id].r = r , node[id].dat = 1;
		if(l != r) {
			int mid = (l + r) / 2;
			node[id].ls = build(l , mid);
			node[id].rs = build(mid + 1 , r);
		}
		return id;
	}
	void change(int root , int pos , int dat) {
		if(node[root].l == node[root].r) {
			node[root].dat = dat;
			return ;
		}
		if(pos <= (node[root].l + node[root].r) / 2)
			change(ls(root) , pos , dat);
		else change(rs(root) , pos , dat);
		node[root].dat = node[ls(root)].dat * node[rs(root)].dat % mod;
	}
	int query(int root , int l , int r) {
		if(r < node[root].l || l > node[root].r)return 1;
		if(l <= node[root].l && r >= node[root].r)return node[root].dat;
		return query(ls(root) , l , r) * query(rs(root) , l , r) % mod;
	}
#undef ls
#undef rs
} segT;
int root;
signed main() {
	n = read() , m = read() , mod = read();
	for(int i = 1 ; i <= n ; i++)
		opt[i] = read() , x[i] = read();
	for(int i = 1 ; i <= m ; i++)
		query[i].l = read() , query[i].r = read() , query[i].id = i;
	sort(query + 1 ,  query + m + 1 , cmp);

	for(int i = 1 ; i <= n ; i++) {
		int id;
		if(mp.find(x[i]) == mp.end())
			id = mp[x[i]] = stk.size() , stk.push_back(new stack<int>);
		else
			id = mp[x[i]];
		auto s = stk[id];
		if(opt[i] == 1) {
			s->push(i);
		} else if(s->size() > 0) {
			pre[i] = s->top() , s->pop();
		}
	}

	root = segT.build(1 , n);

	for(int i = 1 , j = 1 ; i <= m ; i++)  {
		while(j <= n && j <= query[i].r) {
			if(opt[j] == 1)
				segT.change(root , j , x[j]);
			else if(pre[j] > 0)
				segT.change(root , pre[j] , 1);
			++j;
		}
		ans[query[i].id] = segT.query(root , query[i].l , query[i].r);
	}
	for(int i = 1 ; i <= m ; i++)
		printf("%lld\n" , ans[i]);

	for(auto i : stk)delete i;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值