题解 :P13004 [GCJ 2022 Finals] Schrödinger and Pavlov

简要题意 :

给定长度为 n 的字符串\(a\), 数组b
每个盒子有一个状态 $ a_i$

  • C : 有一只猫
  • . : 没有猫。
  • ? :可能有猫,有猫和没有猫的概率相同。

有一条$ i -> b_i $的通道, 由 \(1\) ~ \(n\) 依次执行操作 :

  • 如果盒子 ( i ) 里没有猫,或者盒子 ( b_i ) 里有猫,则不操作
  • 否则(( i ) 有猫且 ( b_i ) 没猫),则盒子 ( i ) 中的猫移动到盒子 ( b_i ) 中。

求最后盒子 ( n ) 中有猫的方案数(对于 ? 的状态,猫的有无视为两种不同方案)

算方案数有点难, 不妨考虑算概率,于是答案就等于

\[概率 \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 \]

  2. 盒子 \(b_i\) 操作后有猫的概率
    \(b_i\) 有猫 = 原本有猫 或 (\(i\) 有猫且 \(b_i\) 原本没猫)

    \[p_{b_i}' = y + x(1-y) = x + y - xy \]

进一步的观察 : 这是一个内向基环树森林,因为如果不和n联通的点不会影响n的答案
基环树DP问题一个常见的做法就是断环,但是由于这里必须从\(1\) ~ \(n\) 依次执行操作,断环会破环这种依赖,但是这题仍然是一种类似断环的做法 :

  • 设s为环上编号最小的点
  • 假设我们断开了s到\(b[s]\)的边, 那么1到s - 1的处理就没有后效性了,这是因为s一定是第一个到环上的点
  • 于是我们枚举s与\(b[s]\)\(0/1\),因为:

\[P(\text{盒子n有猫}) = \sum_{状态} P(状态) \times P(\text{盒子n有猫} \mid 状态) \]

我们通过一种加权的方法让s不会再被后面的点影响

  • 补充 : 我觉得这里只能是环上最小的点,这也是题解区两种不同的说法之一,假如我们选了环上不是最小编号的点d,那处理d的时候已经处理了s,那对于s,后面的点还有可能影响到s本身,选s的好处不就是s是环上唯一被环外的节点的指向的点吗

那么做法呼之欲出,代码有一些细节 :

  • 要从n开始找环,这样的环一定是可以对n产生影响的
  • 在处理s的时候,我们应该记录一个概率t表示p1,p2条件成立的概率
#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  ileu5fary  阅读(4)  评论(0)    收藏  举报