Sequence

Sequence

贪心,集合,转化

题意

给定长度为 \(n\) 的 01 序列 \(s_1, s_2, \dots, s_n\),定义

\[p_i = \sum_{j=1}^{i} [s_i \ne s_j] \]

其中 \([\,]\) 是艾弗森括号。
现在给定整数 \(n\) 以及多重集 \(\{p'_1, p'_2, \dots, p'_n\}\),它表示 \(\{p_1, p_2, \dots, p_n\}\) 的所有值构成的多重集。
求满足条件的 01 序列 \(s\) 的数量,答案对 \(998244353\) 取模。保证至少存在一个合法序列。

关键观察

1. \(p_i\) 的等价定义

因为只有 0 和 1 两种字符,所以对于位置 \(i\):

  • 若 \(s_i = 0\),则 \(p_i\) 就是前 \(i\) 个字符中 \(1\) 的个数。
  • 若 \(s_i = 1\),则 \(p_i\) 就是前 \(i\) 个字符中 \(0\) 的个数。

设当前前缀中 \(0\) 的个数为 \(a\),\(1\) 的个数为 \(b\)。那么:

  • 选择 \(0\) 会产生 \(p = b\)
  • 选择 \(1\) 会产生 \(p = a\)

2. 取反对称性

如果当前 \(a > b\),将整个序列取反(\(0\) 变 \(1\),\(1\) 变 \(0\)),所有 \(p_i\) 不变,因为条件 \([s_i \ne s_j]\) 在取反后依然成立。
因此可以强制规定 \(a \le b\),当 \(a > b\) 时交换 \(a\) 和 \(b\)。

3. 每一步的选择是被迫的

在 \(a \le b\) 的前提下:

  • 当 \(a < b\) 时:

    • 选 \(1\) 产生 \(p = a\)
    • 选 \(0\) 产生 \(p = b\)

    如果剩余多重集中还有 \(a\),那么这一步必须选 \(1\),消耗掉一个 \(a\)。
    原因:若选 \(0\),则 \(a\) 变为 \(a+1\),之后所有产生的 \(p\) 值都至少为 \(a+1\),那个剩余的 \(a\) 就永远无法产生了。
    如果剩余中已经没有 \(a\),则不能选 \(1\),只能选 \(0\),消耗一个 \(b\)。

  • 当 \(a = b\) 时:

    • 选 \(0\) 或选 \(1\) 都产生 \(p = a\)。
    • 必须消耗一个 \(a\)。
    • 两种选择会导向完全对称的后续构造,方案数乘以 \(2\)。

因此,整个构造过程中唯一有分支的地方就是 \(a = b\),且分支完全对称。

4. 为什么贪心不会“断”?

如果输入的 \(p'\) 确实来自某个合法序列,那么在那个合法序列中,每一步产生的 \(p\) 值正好就是算法每一步需要消耗的值。
因为算法的选择是唯一被迫的(除了 \(a = b\) 时的对称分支),任何合法序列在规范化后都必须遵循同样的选择。
所以,如果存在合法序列,算法消耗 \(p\) 值的顺序(在多重集意义下)与它完全一致,不会中途缺少需要的值。
如果中途发现需要的值不存在,说明根本不存在合法序列。题目保证至少有一个合法序列,所以实际不会断。

答案计算

设构造过程中出现 \(a = b\) 的次数为 \(k\)。
如果能够构造完所有位置,并恰好消耗完给定多重集中的全部元素,答案就是 \(2^k \bmod 998244353\);否则答案为 \(0\)。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;

void solve() {
    int n; cin >> n;
    vector<int> cnt(n + 1, 0);
    for(int i = 0; i < n; i ++) {
        int x; cin >> x;
        cnt[x] ++;
    }

    int a = 0, b = 0;
    ll ans = 1;
    for(int i = 1; i <= n; i ++) {
        if(a == b) {
            if(cnt[a] <= 0) {cout << "0\n"; return;}
            b ++; cnt[a] --;
            ans = ans * 2 % MOD;
        }
        else {
            if(cnt[a] > 0) cnt[a] --, b ++;
            else {
                if(cnt[b] <= 0) {cout << "0\n"; return;}
                cnt[b] --; a ++;
            }
        }
    }
    for(int i = 0; i <= n; i ++) {
        if(cnt[i] != 0) {cout << "0\n"; return;}
    }
    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int _ = 1; //cin >> _;
    while(_--) solve();
    return 0;
}
posted @ 2026-10-03 09:47  thenrify  阅读(8)  评论(0)    收藏  举报