题解 CF981G Magic multisets
$\texttt{update 2022.10.25}$ 修改了下格式。
题意
给定 $n$ 个可重集,初始为空。向可重集中加入一个正整数 $x$,如果 $x$ 已经在该可重集出现,则可重集中每一个元素出现次数翻倍,否则直接加入。
$q$ 次操作:
-
下标在 $\left[l,r\right]$ 的可重集加入数 $x$
-
查询下标在 $\left[l,r\right]$ 的可重集大小的和,对 $998244353$ 取模。
数据范围:$1\le n,q\le2\times 10^5,1\le x\le n$
题解
这个题跟 P8416 处理方法有点像。
有 $1\le x\le n$,考虑每个 $x$ 分开处理,用 $0$ 和 $1$ 表示可重集中有没有 $x$。
这个问题相当于给定一个序列,加入数 $x$ 相当于把序列区间推平成 $1$。
这种区间推平问题很自然地想到颜色段均摊,即 ODT。
有个很经典的 trick 就是用 ODT 把区间推平转成区间加。
于是我们开 $n$ 棵珂朵莉树维护集合是否有 $x$,再用一棵线段树维护集合大小的和即可。
这个题可以先把区间的集合大小乘 $2$,再把序列上为 $0$ 的数除以 $2$ 再加 $1$,所以用线段树维护区间乘区间加即可。
然后这个复杂度的正确性,因为颜色段均摊每次 split 最多增加 $O(1)$ 个区间,每个区间删除 $O(1)$ 次,因为最多 $q$ 次操作,所以区间总数是 $O(q)$ 级别的,所以总时间复杂度为 $O(n+q\log n)$。
注意 split 要先分裂 $r+1$,不然可能会越界,经典错误了属于是。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, mod = 998244353, inv2 = mod + 1 >> 1;
int n, q;
namespace segment_tree {
#define ls x << 1
#define rs x << 1 | 1
int tagm[N << 2], taga[N << 2], sum[N << 2];
void build(int x, int l, int r) {
if(l == r) return;
int mid = l + r >> 1;
taga[x] = 0, tagm[x] = 1;
build(ls, l, mid), build(rs, mid + 1, r);
}
void pushup(int x) { sum[x] = (sum[ls] + sum[rs]) % mod; }
void pusha(int x, int l, int r, int v) {
if(!v) return;
taga[x] = (taga[x] + v) % mod;
sum[x] = (sum[x] + 1ll * (r - l + 1) * v) % mod;
}
void pushm(int x, int v) {
if(v == 1) return;
tagm[x] = 1ll * tagm[x] * v % mod;
taga[x] = 1ll * taga[x] * v % mod;
sum[x] = 1ll * sum[x] * v % mod;
}
void pushdown(int x, int l, int r) {
int mid = l + r >> 1;
pushm(ls, tagm[x]), pushm(rs, tagm[x]);
tagm[x] = 1;
pusha(ls, l, mid, taga[x]), pusha(rs, mid + 1, r, taga[x]);
taga[x] = 0;
}
void updatea(int x, int l, int r, int L, int R, int v) {
if(L <= l && r <= R) { pusha(x, l, r, v); return; }
pushdown(x, l, r);
int mid = l + r >> 1;
if(mid >= L) updatea(ls, l, mid, L, R, v);
if(mid < R) updatea(rs, mid + 1, r, L, R, v);
pushup(x);
}
void updatem(int x, int l, int r, int L, int R, int v) {
if(L <= l && r <= R) { pushm(x, v); return; }
pushdown(x, l, r);
int mid = l + r >> 1;
if(mid >= L) updatem(ls, l, mid, L, R, v);
if(mid < R) updatem(rs, mid + 1, r, L, R, v);
pushup(x);
}
int query(int x, int l, int r, int L, int R) {
if(L <= l && r <= R) return sum[x];
pushdown(x, l, r);
int mid = l + r >> 1, res = 0;
if(mid >= L) res = (res + query(ls, l, mid, L, R)) % mod;
if(mid < R) res = (res + query(rs, mid + 1, r, L, R)) % mod;
return res;
}
} using namespace segment_tree;
struct old_driver_tree {
struct odt {
int l, r;
mutable int v;
odt(int L = 0, int R = 0, int V = 0) { l = L, r = R, v = V; }
friend bool operator < (const odt &qwq, const odt &awa) {
return qwq.l < awa.l;
}
};
set<odt> s;
void init() { s.insert(odt(1, n)); }
#define IT set<odt>::iterator
#define pii pair<int, int>
IT split(int x) {
IT it = s.lower_bound(odt(x, 0, 0));
if(it != s.end() && it -> l == x) return it;
--it;
int L = it -> l, R = it -> r, V = it -> v;
s.erase(it);
s.insert(odt(L, x - 1, V));
return s.insert(odt(x, R, V)).first;
}
void assign(int l, int r) {
updatem(1, 1, n, l, r, 2);
IT itr = split(r + 1), itl = split(l);
for(IT it = itl; it != itr; ++it)
if(!(it -> v))
updatem(1, 1, n, it -> l, it -> r, inv2),
updatea(1, 1, n, it -> l, it -> r, 1);
s.erase(itl, itr);
s.insert(odt(l, r, 1));
}
} t[N];
int main() {
scanf("%d%d", &n, &q);
build(1, 1, n);
for(int i = 1; i <= n; i++)
t[i].init();
while(q--) {
int opt, l, r, x;
scanf("%d%d%d", &opt, &l, &r);
if(opt == 1) {
scanf("%d", &x);
t[x].assign(l, r);
}
else printf("%d\n", query(1, 1, n, l, r));
}
return 0;
}

浙公网安备 33010602011771号