P6630 [ZJOI2020] 传统艺能 题解
Werner_Yin · · 题解
同步于我的 Blog
2023.6.10 UPD: 3 * 3 的式子原来有笔误,感谢 灰鹤在此 和 llzer 的指出。
有一棵广义线段树,每个节点有一个
m 值。一开始tag数组均为0 ,Bob 会执行k 次操作,每次操作等概率随机选择区间[l, r] 并执行MODIFY(root,1,n,l,r);。 最后所有Node中满足tag[Node]=1的期望数量。n \le 2\times 10^5
看着题解想锤人的题。。。
很多题解都不讲清状态的设计,以及为什么只要一个
首先,考虑根据期望的线性性计算答案,计算每个节点最后有 tag 的概率,比较显然的事情就是 tag 只有两种:祖先的 tag(是否存在一个祖先的 tag = 1) 和自己的 tag。接着我们可以只记录这两种 tag,并将每次操作的节点分类:
- A. 父亲区间和操作区间没有交集:什么也不会发生变化。
- B. 父亲区间被操作区间包含:祖先的 tag = 1,自己的 tag 不变。
-
父亲区间和操作区间有交(但是不被包含)
- C. 自己与操作区间没有交集:自己的 tag |= 祖先的 tag, 祖先的 tag = 0。
- D. 自己区间被操作区间包含:祖先的 tag = 0, 自己的 tag = 1。
- E. 自己区间和操作区间有交:祖先的 tag = 自己的 tag = 0。
然后按照编号 ABCDE 计算方案数。
关于方案数的计算,我们可以发现,单个节点与操作区间 没有交集/被包含/没有被包含但是有交集 的概率是比较好计算的。 那么
A,B 可以从父亲节点继承过来,C 就是自己的没有交集的概率-父亲没有交集的概率,D 就是自己被包含的概率-父亲被包含的概率,最后一个,E =1-A-B-C-D 即可。
之后魔幻的事情就来了,大部分题解都直接说维护一个
最朴素直接的想法应该是设
这个可以直接使用
但是为什么很多题解都拿的
我们可以注意到,最后的答案就是
具体地,将
然后可以使用
因为
代码(gitee)
#include <bits/stdc++.h>
#define eb emplace_back
#define ep emplace
#define fi first
#define se second
#define in read<int>()
#define lin read<ll>()
#define rep(i, x, y) for(int i = (x); i <= (y); i++)
#define per(i, x, y) for(int i = (x); i >= (y); i--)
using namespace std;
using ll = long long;
using db = double;
using pii = pair < int, int >;
using vec = vector < int >;
using veg = vector < pii >;
template < typename T > T read() {
T x = 0; bool f = 0; char ch = getchar();
while(!isdigit(ch)) f |= ch == '-', ch = getchar();
while(isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
template < typename T > void chkmax(T &x, const T &y) { x = x > y ? x : y; }
template < typename T > void chkmin(T &x, const T &y) { x = x < y ? x : y; }
const int N = 2e5 + 10;
const int mod = 998244353;
const int inv2 = (mod + 1) >> 1;
int n, K, iv, ans;
ll qp(ll x, int t) { ll res = 1; for(; t; t >>= 1, x = x * x % mod) if(t & 1) res = res * x % mod; return res; }
struct node {
int a, b, c; // a : 不交, b : 被包含, c : 不被包含但是有交
node(int l, int r) {
a = (1ll * l * (l - 1) / 2 % mod * iv % mod + 1ll * (n - r + 1) * (n - r) / 2 % mod * iv % mod) % mod;
b = 1ll * l * (n - r + 1) % mod * iv % mod;
c = (mod * 2 - a - b + 1) % mod;
} node(int _a, int _b, int _c) : a(_a), b(_b), c(_c) { }
node() { a = b = c = 0; }
};
struct mat {
int a[4][4];
mat() { memset(a, 0, sizeof a); }
int* operator [](int x) { return a[x]; }
friend mat operator * (mat a, mat b) {
mat c;
rep(i, 0, 3) rep(j, 0, 3) if(a[i][j])
rep(k, 0, 3) c[i][k] = (c[i][k] + 1ll * a[i][j] * b[j][k] % mod) % mod;
return c;
}
} t;
void solve(int l, int r, const node &lst) {
t = mat(); node c(l, r);
ll A = lst.a, B = lst.b, C = (c.a - lst.a + mod) % mod, D = (c.b - lst.b + mod) % mod, E = (1ll - A - B - C - D + mod * 4ll) % mod;
t[0][0] = (A + C + E) % mod; t[0][1] = D; t[0][2] = B;
t[1][0] = E; t[1][1] = (A + C + D) % mod; t[1][3] = B;
t[2][0] = E; t[2][1] = (C + D) % mod; t[2][2] = (A + B) % mod;
t[3][0] = E; t[3][1] = (C + D) % mod; t[3][2] = 0; t[3][3] = (A + B) % mod;
mat res = t; for(int v = K - 1; v; v >>= 1, t = t * t) if(v & 1) res = res * t;
ans = (ans + res[0][1]) % mod; ans = (ans + res[0][3]) % mod;
if(l == r) return; int mid = in; solve(l, mid, c); solve(mid + 1, r, c);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("1.in", "r", stdin);
#endif
n = in, K = in; iv = qp(1ll * n * (n + 1) / 2 % mod, mod - 2);
solve(1, n, node(0, 0, 1)); printf("%d\n", ans); return 0;
}