随机坏道
原题链接:洛谷U249552
Limits
时间限制:1000ms
内存限制:256MB
Description
小F有一个磁盘,磁盘上有 n n n个扇区,每个扇区末尾都会有 L , R L,R L,R两个指针。每个指针可以指向一个扇区或者留空。这些扇区恰好形成了一颗二叉树。现在有一个读取程序,它将从 1 1 1号扇区开始读取,读到扇区末时会等概率地任选一个指针进行跳转。如果选择了空指针,那么不会发生跳转,读取程序退出。
有一天,小F发现有一个扇区损坏了。具体而言, k k k号扇区的 L L L指针指向了 m m m号扇区。现在小F想知道,扇区损坏后,读取程序退出前发生跳转次数的数学期望。答案对 998244353 998244353 998244353取模。
Input
第1行, 1 1 1个数 n n n,表示扇区个数;
第2行, n n n个数,依次表示各扇区的尾部的 L L L指针指向的扇区编号, L 1 , L 2 , … , L n L_1,L_2,…,L_n L1,L2,…,Ln;
第3行, n n n个数,依次表示各扇区的尾部的 R R R指针指向的扇区编号, R 1 , R 2 , … , R n R_1,R_2,…,R_n R1,R2,…,Rn,
第4行, 2 2 2个数,分别表示 k , m k,m k,m;
注意:扇区编号从 1 1 1开始, 0 0 0表示空指针;
Output
一个数,表示所求数学期望对 998244353 998244353 998244353取模的值;
Samples
Sample Input 1
6
2 4 0 0 0 0
3 5 6 0 0 0
2 3
Sample Output 1
124780546
提示:即 15 8 \frac{15}{8} 815。
Sample Input 2
6
2 4 0 0 0 0
3 5 6 0 0 0
4 2
Sample Output 2
582309208
提示:即 25 12 \frac{25}{12} 1225。
Data range
测试点 | n n n | 特殊性质 |
---|---|---|
1,2 | ≤ 500 \le 500 ≤500 | 扇区损坏后一定可以停止读取 |
3,4 | ≤ 500 \le 500 ≤500 | 无 |
5,6 | ≤ 1 0 6 \le 10^6 ≤106 | 扇区损坏后一定可以停止读取 |
7,8,9,10 | ≤ 1 0 6 \le 10^6 ≤106 | 无 |
对于100%数据, 1 ≤ k ≤ n ≤ 1 0 6 1\le k\le n\le 10^6 1≤k≤n≤106, 0 ≤ L i , R i , m ≤ n 0\le L_i,R_i,m\le n 0≤Li,Ri,m≤n。
Hints
有理数 a b \frac{a}{b} ba对质数 P P P取模的结果是整数 c c c, c c c满足 0 ≤ c < P 0\le c<P 0≤c<P且 c ⋅ b ≡ a ( m o d P ) c \cdot b\equiv a(mod\ P) c⋅b≡a(mod P)。
Solution
难度:提高+/省选-
40% 简单递推
记从
x
x
x号扇区开始读取直到退出时的跳转次数的数学期望为
E
(
X
)
E(X)
E(X),则容易得到递推公式
E
(
X
)
=
{
1
2
E
(
L
X
)
+
1
2
E
(
R
X
)
+
1
,
L
X
≠
0
且
R
X
≠
0
1
2
[
E
(
L
X
)
+
1
]
,
L
X
=
0
且
R
X
≠
0
1
2
[
E
(
L
R
)
+
1
]
,
L
X
≠
0
且
R
X
=
0
0
,
L
X
=
0
且
R
X
=
0
E(X)=\left\{ \begin{aligned} &\frac{1}{2}E(L_X)+\frac{1}{2}E(R_X)+1, L_X \neq 0且R_X \neq 0\\ &\frac{1}{2}\left[E(L_X)+1\right], L_X = 0且R_X \neq 0\\ &\frac{1}{2}\left[E(L_R)+1\right], L_X \neq 0且R_X = 0\\ &0, L_X = 0且R_X = 0 \end{aligned} \right.
E(X)=⎩
⎨
⎧21E(LX)+21E(RX)+1,LX=0且RX=021[E(LX)+1],LX=0且RX=021[E(LR)+1],LX=0且RX=00,LX=0且RX=0
如果我们规定 E ( 0 ) = − 1 E(0)=-1 E(0)=−1,则上式可以化简为 E ( X ) = 1 2 E ( L X ) + 1 2 E ( R X ) + 1 E(X)=\frac{1}{2}E(L_X)+\frac{1}{2}E(R_X)+1 E(X)=21E(LX)+21E(RX)+1
对于1,2,5,6四个数据点,直接从底向上(到1号点)递推计算即可。也可以自顶向下递归操作。复杂度 O ( n ) O(n) O(n);
60% 高斯消元
硬盘有 n n n个扇区,则我们可以列出 n n n个线性方程。容易看出可以应用Gauss-Jordan消元法来解方程组,最终得到 E ( 1 ) E(1) E(1)的值。复杂度 O ( n 3 ) O(n^3) O(n3)。
该方法可以通过3,4数据点,但有可能卡常数。
100% 递推+拓扑排序
数据范围的特殊条件即描述了一种有向无环图(DaG)的情形。在这种情况下,使用拓扑排序逐步向前递推计算,其复杂度近似是 O ( n ) O(n) O(n)的。
即便不是DaG,我们也容易看出其中最多有一个环。该环一定包含 k k k号扇区,从 k k k号扇区遍历整个图,即可得到整个环,不满足递推性质而需要移项解方程的情形也发生在 k k k号扇区对应的方程中。
因此,我们可以得到最终方法:
- 应用拓扑排序,求出部分扇区的期望。如果没有环,这一步就能求出所有扇区的期望;
- 从 k k k号扇区开始找环,逐步展开方程,从而求出 k k k号扇区的期望;
- 继续拓扑排序,求出所有扇区的期望;
展开方程的方法:
在等式 E ( X ) = 1 2 E ( L X ) + 1 2 E ( R X ) + 1 E(X)=\frac{1}{2}E(L_X)+\frac{1}{2}E(R_X)+1 E(X)=21E(LX)+21E(RX)+1中, L X , R X L_X,R_X LX,RX中有且仅有一个的期望是已知的,因此该式可以改写为 E ( X ) = 1 2 E ( C h X ) + C E(X)=\frac{1}{2}E(Ch_X)+C E(X)=21E(ChX)+C,其中 C h X Ch_X ChX表示X的某个孩子, C C C表示常数。
从 X = k X=k X=k开始搜索,每次令 X : = C h X X:=Ch_X X:=ChX,直至搜出整个环。
如果环中有 h h h个扇区,则最后的方程为 E ( k ) = ( 1 2 ) h E ( k ) + C ′ E(k)=(\frac{1}{2})^{h}E(k)+C' E(k)=(21)hE(k)+C′,可得 E ( k ) = C ′ 1 − 1 2 h E(k)=\frac{C'}{1-\frac{1}{2^h}} E(k)=1−2h1C′。
Code
#include<iostream>
#include<vector>
#include<queue>
#include<cassert>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
const int MAXN = 1000000;
const ll NOT_CEHCKED = -2;
ll E[MAXN + 1];
int deg[MAXN + 1];
pair<int,int> gl[MAXN + 1];
vector<int> gl_rev[MAXN + 1];
bool inque[MAXN + 1];
ll my_pow(ll a, ll x) {
ll ret = 1;
while (x) {
if (x & 1)ret = ret * a % MOD;
x >>= 1;
a = a * a % MOD;
}
return ret;
}
const ll inv2 = my_pow(2, MOD - 2);
int main()
{
int n, k, m, cnt = 0;
//输入
ios::sync_with_stdio(false);
cin >> n;
E[0] = MOD - 1; //-1在模MOD意义下是MOD-1
for (int i = 1; i <= n; i++) {
E[i] = NOT_CEHCKED; //NOT_CEHCKED表示尚未计算
cin >> gl[i].first;
}
for (int i = 1; i <= n; i++) {
cin >> gl[i].second;
}
cin >> k >> m; gl[k].first = m;
//构建反向图用于拓扑排序
for (int u = 1; u <= n; u++) {
if (gl[u].first) {
deg[u]++;
gl_rev[gl[u].first].push_back(u);
}
if (gl[u].second) {
deg[u]++;
gl_rev[gl[u].second].push_back(u);
}
}
queue<int> q;
//叶节点特殊处理
auto upd_node=[&](int u)->void{
for (auto v : gl_rev[u]) {
deg[v]--;
if (deg[v] == 0 && !inque[v]) {
q.push(v);
inque[v] = true;
}
}
};
for (int u = 1; u <= n; u++) {
if (deg[u]) continue;
q.push(u); inque[u]=true;
}
//拓扑排序计算期望
auto topo_func = [&]()->void {
while (!q.empty()) {
int u = q.front(); q.pop(); inque[u] = false;
//E[u] = (E[uL]+E[uR])/2+1,规定E[0]=-1
int lch = gl[u].first, rch = gl[u].second;
E[u] = (((E[lch] + E[rch])%MOD) * inv2 % MOD + 1) % MOD;
upd_node(u);
}
};
topo_func();
//已有结果则直接输出
if (E[1] != NOT_CEHCKED) {
cout << E[1] << endl;
return 0;
}
//查找环,总是从k号节点开始查找
//E[k]=p*E[?]+c
ll p = inv2, c = (E[gl[k].second] * inv2 + 1) % MOD;
int unknown = gl[k].first;
while (unknown != k) {
int chd_known = gl[unknown].first, chd_unknown = gl[unknown].second;
if (E[chd_known] == NOT_CEHCKED) {
swap(chd_known, chd_unknown);
}
//assert(E[chd_known] != NOT_CEHCKED && E[chd_unknown] == NOT_CEHCKED);
ll c0 = (E[chd_known] * inv2 + 1) % MOD;
c = (c + (p * c0 % MOD)) % MOD;
p = p * inv2 % MOD;
unknown = chd_unknown;
}
//此时E[k]=p*E[k]+c,E[k]=c/(1-p);
ll fm = (1 - p + MOD) % MOD;
E[k] = c * my_pow(fm, MOD - 2) % MOD;
upd_node(k);
//再次拓扑排序计算
topo_func();
cout << E[1] << endl;
return 0;
}
Postscript
关于分数取模,实际上可以用乘法逆元来描述:
根据费尔马小定理
a p − 1 ≡ 1 ( m o d p ) , p 为质数, a , p 互质 a^{p-1}≡1(mod\ p), p为质数,a,p互质 ap−1≡1(mod p),p为质数,a,p互质
可知
a b ≡ a ⋅ b − 1 ≡ a ⋅ b p − 2 ( m o d p ) , p 为质数, b , p 互质 \frac{a}{b}\equiv a \cdot b^{-1}\equiv a \cdot b^{p-2}(mod\ p), p为质数,b,p互质 ba≡a⋅b−1≡a⋅bp−2(mod p),p为质数,b,p互质
其他求乘法逆元的方法还有
- 线性递推
- 拓展欧几里得
请自行学习。