Sugar Sweet II
Sugar Sweet II
有向基环树,期望
题意
有 \(n\) 个孩子,第 \(i\) 个孩子初始有 \(a_i\) 袋糖。有 \(n\) 个事件以均匀随机顺序发生,第 \(i\) 个事件为:
- 若第 \(i\) 个孩子当前糖数严格少于第 \(b_i\) 个孩子,则第 \(i\) 个孩子额外获得 \(w_i\) 袋糖;否则什么也不发生。
求所有事件结束后每个孩子糖数的期望,模 \(10^9+7\)。
建图:有向基环树森林
把每个孩子 \(i\) 看成一个点,连有向边 \(i \to b_i\)。每个点出度为 \(1\),因此整个图是有向基环树森林(functional graph)。
关键观察:每个事件只增加自己的糖,依赖关系只沿着 \(i \to b_i\) 单向传播。所以不需要对基环树做复杂的环上 DP。
三类分类
对每个点 \(i\),令 \(v = b_i\),比较 \(a_i\) 与 \(a_v\)、\(a_v + w_v\):
A 类:\(a_i < a_v\)
无论 \(v\) 是否成功,\(v\) 当前糖数至少为 \(a_v > a_i\),所以 \(i\) 一定成功:
B 类:\(a_i \ge a_v + w_v\)
无论 \(v\) 是否成功,\(v\) 当前糖数最多为 \(a_v + w_v \le a_i\),所以 \(i\) 一定失败:
C 类:\(a_v \le a_i < a_v + w_v\)
此时 \(i\) 成功当且仅当:
- \(v\) 成功;
- 且事件 \(v\) 在事件 \(i\) 之前执行。
即
其中 \(I_i\) 表示事件 \(i\) 是否成功,\(pos_i\) 表示事件 \(i\) 在随机排列中的位置。
沿反向边传播
建反向边:
从所有 A 类点开始做 BFS / DFS。A 类点深度为 \(0\),沿反向边每走一步深度加 \(1\)。
- A 类点自己深度为 \(0\),概率为 \(1\);
- 若一个 C 类点 \(i\) 到最近的 A 类祖先的距离为 \(d\)(边数),则成功概率为 \(\dfrac{1}{(d+1)!}\);
- B 类点以及无法到达 A 类的纯环上的点,概率均为 \(0\)。
最终:
模意义下用逆阶乘 invfact 即可。
为什么 C 类概率是 \(1/(d+1)!\)
设从 \(i\) 沿 \(b\) 指针往上游走,直到遇到一个 A 类点(恒成功点)为止,路径为:
其中:
- \(x_0\) 是 A 类点,恒成功;
- 每个 \(x_j\)(\(j \ge 1\))都是 C 类点,且 \(b_{x_j} = x_{j-1}\);
- \(d\) 是边数,因此链上一共有 \(d+1\) 个点。
对于 C 类点 \(x_j\),它成功当且仅当:
- 它的父亲 \(x_{j-1}\) 成功;
- 事件 \(x_{j-1}\) 在事件 \(x_j\) 之前执行。
因此要让 \(i = x_d\) 成功,必须同时满足:
也就是说,这 \(d+1\) 个事件在随机排列中必须恰好按顺序
出现。
只看这 \(d+1\) 个事件的相对顺序,共有 \((d+1)!\) 种等可能情况,而满足上述固定顺序的只有 \(1\) 种。因此:
充分性:若链上事件按 \(x_0, x_1, \dots, x_d\) 的顺序发生,则 \(x_0\) 恒成功;轮到 \(x_1\) 时,父亲 \(x_0\) 已经成功且糖数满足条件,所以 \(x_1\) 成功;依次类推,最终 \(i = x_d\) 成功。
必要性:若 \(i\) 成功,则 \(x_{d-1}\) 必须成功且先于 \(i\) 发生;再递归,得到整条链必须按祖先到后代的顺序发生。
所以二者等价,概率为 \(\dfrac{1}{(d+1)!}\)。
分叉不影响单个点的概率
反向边图里,一个点 \(v\) 可能有很多"孩子" \(i\) 满足 \(b_i = v\),反向看会分叉。但这不影响单个点成功概率是 \(1/(d+1)!\),因为:
每个点 \(i\) 只依赖它唯一的父亲 \(b_i\),不依赖兄弟。
虽然反向看会分叉,但从任意一个点沿 \(b\) 指针往上游走,路径是唯一的。对于某个点 \(i\),它要成功,只要求它自己到最近 A 类祖先这条唯一链上的事件按固定顺序发生。兄弟节点的事件怎么排、是否成功,都不会改变这条链上 \(d+1\) 个事件的相对顺序概率。
例子
假设 \(x_0\) 是 A 类点,\(b_{x_1} = x_0\),\(b_{x_2} = x_0\),\(b_{x_3} = x_1\)。反向边图为:
- \(x_1\) 的链:\(x_0, x_1\),长度 \(d = 1\),成功概率 \(\dfrac{1}{2!}\);
- \(x_2\) 的链:\(x_0, x_2\),长度 \(d = 1\),成功概率 \(\dfrac{1}{2!}\);
- \(x_3\) 的链:\(x_0, x_1, x_3\),长度 \(d = 2\),成功概率 \(\dfrac{1}{3!}\)。
注意 \(x_1\) 和 \(x_2\) 是兄弟,但计算 \(x_1\) 成功时不需要管 \(x_2\)。在随机排列中,\(x_2\) 的事件可以出现在任意位置,它只是"插入"到 \(x_0, x_1\) 的相对顺序中,不会改变 \(x_0\) 在 \(x_1\) 前这一相对顺序的概率。
所以 BFS 传播仍然正确:分叉只是让 BFS 同时访问多个分支,每个分支的深度按各自路径计算,不会出现"多条路径同时决定一个点"的情况。
算法流程
- 读入 \(a_i, b_i, w_i\);
- 对每个 \(i\),令 \(v = b_i\),比较 \(a_i\) 与 \(a_v\)、\(a_v + w_v\),标记为 A / B / C 类;
- 建反向边 \(v \to i\);
- 所有 A 类点入队,
vis = 1,pro = 1,dist = 0; - BFS:对队首 \(u\) 的每个孩子 \(v\),若 \(v\) 是 C 类且未访问,则
dist[v] = dist[u] + 1,pro[v] = invfact[dist[v] + 1],入队; - 对每个 \(i\),输出 \(a_i + w_i \cdot pro[i] \bmod (10^9+7)\)。
复杂度
每个点只会被访问一次,反向边总数为 \(n\),因此单组数据:
总复杂度:
满足 \(\sum n \le 5 \cdot 10^5\) 的限制。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using tpi = tuple<int, int, int>;
const ll MOD = 1e9 + 7;
const int N = 1e6;
ll modpow(ll a, ll b) {
ll r = 1;
while(b) {
if(b & 1) r = r * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return r;
}
ll fact[N], invfact[N];
void init() {
fact[0] = 1;
for(int i = 1; i < N; i ++) fact[i] = fact[i - 1] * i % MOD;
invfact[N - 1] = modpow(fact[N - 1], MOD - 2);
for(int i = N - 2; i >= 0; i --) invfact[i] = invfact[i + 1] * (i + 1) % MOD;
}
void solve() {
int n; cin >> n;
vector<vector<int>> g(n + 1, vector<int>());
vector<ll> a(n + 1), w(n + 1);
vector<int> b(n + 1);
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i <= n; i ++) cin >> b[i];
for(int i = 1; i <= n; i ++) cin >> w[i];
vector<int> type(n + 1, -1); // 1必定发生 0必定不发生 2可能发生
for(int i = 1; i <= n; i ++) {
int v = b[i];
if(a[i] < a[v]) type[i] = 1;
else if(a[i] >= a[v] + w[v]) type[i] = 0;
else type[i] = 2;
g[v].push_back(i);
}
vector<int> dist(n + 1, 0);
vector<int> vis(n + 1, 0);
vector<ll> pro(n + 1, 0);
queue<int> q;
for(int i = 1; i <= n; i ++) {
if(type[i] == 1) q.push(i), vis[i] = 1, pro[i] = 1;
}
while(!q.empty()) {
int u = q.front(); q.pop();
for(int v : g[u]) {
if(vis[v] || type[v] != 2) continue;
dist[v] = dist[u] + 1;
pro[v] = invfact[dist[v] + 1];
vis[v] = 1;
q.push(v);
}
}
for(int i = 1; i <= n; i ++) {
ll ans = (a[i] + (w[i] % MOD) * pro[i]) % MOD;
cout << ans << " \n"[i == n];
}
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
init();
int _ = 1; cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号