题解 :P13004 [GCJ 2022 Finals] Schrödinger and Pavlov
简要题意
给定长度为 \(n\) 的字符串 \(a\),数组 \(b\) 每个盒子有一个状态 \(a_i\)
C:有一只猫.:没有猫。?:可能有猫,有猫和没有猫的概率相同。
有一条 \(i \rightarrow b_i\) 的通道,由 \(1 \sim n\) 依次执行操作:
- 如果盒子 \(i\) 里没有猫,或者盒子 \(b_i\) 里有猫,则不操作。
- 否则($ i $ 有猫且 $ b_i $ 没猫),则盒子 $ i $ 中的猫移动到盒子 $ b_i $ 中。
求最后盒子 $ n $ 中有猫的方案数(对于 ? 的状态,猫的有无视为两种不同方案)
算方案数有点难,不妨考虑算概率,于是答案就等于
\[P \times 2^{? \text{ 的个数}}
\]
将每个盒子的初始状态视为概率:
C:\(p_i = 1\).:\(p_i = 0\)?:\(p_i = \frac{1}{2}\)
显然这是一个有 \(n\) 个点,\(n\) 条边的有向基环树。
我们先考虑如果是树怎么做
约定 \(p_i\) 表示目前盒子 \(i\) 有猫的概率,对于第 \(i\) 步操作:
- 盒子 \(i\) 操作后有猫的概率:猫留在 \(i\) 的条件是 \(i\) 有猫且 \(b_i\) 有猫(有猫则不移动)
\[p_i' = x \cdot y
\]
- 盒子 \(b_i\) 操作后有猫的概率:\(b_i\) 有猫等于原本有猫或(\(i\) 有猫且 \(b_i\) 原本没猫)
\[p_{b_i}' = y + x(1-y) = x + y - xy
\]
进一步的观察:这是一个内向基环树森林,因为如果不和 \(n\) 联通的点不会影响 \(n\) 的答案。
基环树 DP 问题一个常见的做法就是断环,但是由于这里必须从 \(1 \sim n\) 依次执行操作,断环会破环这种依赖,但是这题仍然是一种类似断环的做法:
- 设 \(s\) 为环上编号最小的点;
- 假设我们断开了 \(s\) 到 \(b_s\) 的边,那么 \(1\) 到 \(s-1\) 的处理就没有后效性了,这是因为 \(s\) 一定是第一个到环上的点;
- 于是我们枚举 \(s\) 与 \(b_s\) 的 \(0/1\),因为:
\[P(\text{盒子n有猫}) = \sum_{\text{状态}} P(\text{状态}) \times P(\text{盒子n有猫} \mid \text{状态})
\]
我们通过一种加权的方法让 \(s\) 不会再被后面的点影响。
补充:我觉得这里只能是环上最小的点,这也是题解区两种不同的说法之一,假如我们选了环上不是最小编号的点 \(d\),那处理 \(d\) 的时候已经处理了 \(s\),那对于 \(s\),后面的点还有可能影响到 \(s\) 本身,选 \(s\) 的好处不就是 \(s\) 是环上唯一被环外的节点的指向的点吗。
那么做法呼之欲出,代码有一些细节:
- 要从 \(n\) 开始找环,这样的环一定是可以对 \(n\) 产生影响的。
- 在处理 \(s\) 的时候,我们应该记录一个概率 \(t\) 表示 \(p_1,p_2\) 条件成立的概率。
#include<bits/stdc++.h>
using namespace std;
// #define int long long
#define rep(i, l, r) for(int i = (l); i <= (r); ++ i)
#define per(i, r, l) for(int i = (r); i >= (l); -- i)
#define fi first
#define se second
#define endl '\n'
#define pii pair<int, int>
#define pb push_back
const int N = 5e3 + 10, mod = 1e9 + 7, inv2 = 500000004;
int vis[N], b[N], p[N], st[N];
int n, top, s;
char a[N];
void dfs(int u) {
if(vis[u]) {
while(top) {
s = min(s, st[top]);
if(st[top] == u) break;
-- top;
}
return ;
}
vis[u] = 1;
st[++ top] = u;
dfs(b[u]);
}
void Jail(int turn) {
cin >> n;
rep(i, 1, n) cin >> a[i];
rep(i, 1, n) cin >> b[i];
s = n + 1, top = 0;
dfs(n);
int ans = 0;
rep(p1, 0, 1) {
rep(p2, 0, 1) {
rep(i, 1, n) {
if(a[i] == 'C') p[i] = 1;
else if(a[i] == '.') p[i] = 0;
else p[i] = inv2;
}
rep(i, 1, s - 1) {
int x = p[i], y = p[b[i]];
p[i] = 1ll * x * y % mod;
p[b[i]] = 1ll * (x + y - 1ll * x * y % mod + mod) % mod;
}
int t = 1;
if(p1) t = 1ll * t * p[s] % mod;
else t = 1ll * t * (1 - p[s] + mod) % mod;
if(p2) t = 1ll * t * p[b[s]] % mod;
else t = 1ll * t * (1 - p[b[s]] + mod) % mod;
p[s] = 1ll * p1 * p2 % mod;
p[b[s]] = 1ll * (p1 + p2 - 1ll * p1 * p2 % mod + mod) % mod;
rep(i, s, n) {
int x = p[i], y = p[b[i]];
p[i] = 1ll * x * y % mod;
p[b[i]] = 1ll * (x + y - 1ll * x * y % mod + mod) % mod;
}
ans = 1ll * (ans + 1ll * t * p[n] % mod) % mod;
}
}
rep(i, 1, n) {
if(a[i] == '?') ans = ans * 2 % mod;
}
cout << "Case #" << turn << ": "<< ans << endl;
rep(i, 1, n) {
b[i] = st[i] = p[i] = vis[i] = 0;
}
}
signed main() {
// freopen("a.in", "r", stdin);
// freopen("a.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T = 1;
cin >> T;
rep(i, 1, T) Jail(i);
return 0;
}

浙公网安备 33010602011771号