题解 :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\) 步操作:

  1. 盒子 \(i\) 操作后有猫的概率:猫留在 \(i\) 的条件是 \(i\) 有猫且 \(b_i\) 有猫(有猫则不移动)

\[p_i' = x \cdot y \]

  1. 盒子 \(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;
} 
posted @ 2026-05-14 15:07  Koswel  阅读(20)  评论(0)    收藏  举报