可重集
题目
题目背景
stoorz 有一个可重集。
有一天他突然发现一个惊人的消息:这个可重集,它居然是可重的!!!
所以他出了这道题。
题目描述
有一个可重集,初始为空集。
有 \(n\) 个操作,每个操作均属于以下两种类型之一:
1 x
:往可重集里加入一个正整数 \(x\)。2 x
:在可重集中删除数字 \(x\)。如果数字 \(x\) 出现了多次只删除一次。如果可重集内没有数字 \(x\),那就忽略这次操作。有 \(m\) 次询问。每次询问给出两个数 \(l,r\),你需要回答如果依次执行第 \(l\) 至第 \(r\) 个操作,最后可重集内的数的乘积 \(\text{mod } p\) 的值。特别的,我们定义空集的乘积为 \(1\)。
输入格式
第一行三个正整数 \(n,m,p\),分别表示操作的数量,询问的次数,模数。
第二行至第 \(n+1\) 行,每行两个正整数 \(opt,x\),表示这次操作的类型,以及操作的数 \(x\)。
最后 \(m\) 行,每行两个正整数 \(l,r\),表示一次询问。
输出格式
输出 \(m\) 行,其中第 \(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\) 次询问询问,操作后他们的可重集分别如下:
\[\{7,6,8\},\{7,6\},\{6,8\},\{7,6\} \]
本题采用捆绑测试。
子任务编号 分值 \(n,m\leq\) 特殊限制 \(1\) \(20\rm pts\) \(10^3\) 无 \(2\) \(20\rm pts\) \(3\times 10^5\) \(p\) 是质数且所有操作的 \(x<p\),所有询问的 \(l=1\) \(3\) \(15\rm pts\) \(3\times 10^5\) \(p\) 是质数,所有询问的 \(l=1\) \(4\) \(20\rm pts\) \(3\times 10^5\) 所有询问的 \(l=1\) \(5\) \(25\rm pts\) \(3\times 10^5\) 无 对于 \(100\%\) 的数据,\(n,m\leq 3\times 10^5\),\(1\leq p\leq 10^9\),每次操作满足 \(opt\in\{1,2\}\),\(1\leq x\leq 10^9\),每次询问满足 \(1\leq l\leq r\leq n\)。
附件下载
example2.zip113.69KB
思路
对于原操作序列,每个删除操作对应的添加操作是一定的.
我们求出删除操作对应的添加操作,设操作\(i\)对应操作\(j\)(\(j<i , opt_i=2 , opt_j=1\)).
设一个数组\(a_n\)(初始为1) , 询问按照右端点排序后从左向右扫描,同时,我们用指针\(j\)遍历操作,若\(opt_j=1\),则\(a_j=x_j\),若\(opt_j=2\)且\(j\)有对应的操作,则\(a_{pre_j}=1\).
对于一个查询\(l,r\),\((\prod^r_{k=l}a_k) \bmod p\)就是该查询的答案.
序列\(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;
}