XXI Open Cup, Grand Prix of Tokyo:B题

前言

逆天计数坐牢局,上来一看10道题全mod 998244353,就知道不对劲。大约前3小时我和队友们在轮番思考除DFJ以外的所有题(我甚至试了无数种dp方法写A,赛后一看题解直接傻眼),后面开始专攻BGI。4小时多,队友开出了B,比赛快结束的时候,我终于过掉了I。赛后我一边看题解一边看队友代码研究B,然而题解有点简略,我一晚上没看懂……今天终于懂了,来写一篇详细的题解。

我真傻,真的。


B. Bit Operation

题意

给定一个01序列a,可以执行n-1次如下操作:选择相邻的两个数a[i]和a[i+1],将它们替换为a[i] & a[i+1]或a[i] | a[i+1](这是两种不同的操作)。问有多少种方案,可以使最后剩下的那一个数是1。

解法

考虑题中给出的操作,选定的两个数只有四种可能的情况:

 0 & 0 = 0, 0 | 0 = 0
 1 & 1 = 1, 1 | 1 = 1
 0 & 1 = 0, 0 | 1 = 1
 1 & 0 = 0, 1 | 0 = 1

可以发现,如果两个数有0有1,&可以看作删掉其中的1,|可以看作删掉其中的0;如果两个数相同,&可以看作取左边的数,|可以看作取右边的数(当然,左右反过来也一样)。那么这个操作就可以等价于:

选择两个相邻的数,删掉其中一个;删左和删右是两种不同的操作。

这样就可以枚举a中每一个1,作为最后留下来的数。接下来考虑把选定的位置左右两端的所有数全部删掉的方案数。先考虑删除a[i](i不是选定的位置)的方案数。如果i=1,那么只能取a[1]和a[2],删除a[1],也就是只有一种方案。i=n与此同理。否则,可以取a[i-1]和a[i],删除a[i],或者取a[i]和a[i+1],删除a[i],也就是有两种方案。即:删掉第一个数的方案只有一种,而删掉其他数的方案都有2种
用p[i]表示选定位置的某一侧有i个数时,把它们全部删掉的方案数。显然,p[0] = 1。由上述结论,这一侧有i个数时,第一次操作有(1+2i)种选择。做完这次操作,还剩i-1个数,全部删掉的方案数就是p[i-1]。所以可以通过

p[0] = 1;
for(int i = 1; i <= n; i++){
    p[i] = p[i - 1] * (i * 2 - 1);
}

得出删掉一侧任意个数的方案数(实际做题注意取模)。

由于左右两端的操作可以相互穿插,假设选定的位置是i,那也就是说总共做了n-1次操作,其中有i-1次是对左边进行的,所以答案要再乘上组合数C(n-1, i-1)。i位置的最终答案就是p[i-1] * p[n-i] * C(n-1, i-1)。最后把所有选定位置的答案加起来即可。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N = 1e6 + 5;
const ll P = 998244353;

int n, a[N];
ll p[N], inv[N], c[N], ans;

ll fastpow(ll x, ll y){
    ll ret = 1;
    while(y){
        if(y & 1) ret = ret * x % P;
        x = x * x % P;
        y >>= 1;
    }
    return ret;
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++){
        scanf("%d", &a[i]);
    }

    //p[i]表示选定的数的某一侧有i个数时,删掉这些数的方案数
    p[0] = 1;
    for(int i = 1; i <= n; i++){
        p[i] = p[i - 1] * (i * 2 - 1) % P;
    }

    //inv[i]是i的逆元
    for(int i = 1; i <= n; i++){
        inv[i] = fastpow((ll)i, P - 2);
    }

    //c[i]表示组合数C(n - 1, i)
    c[0] = 1;
    for(int i = 1; i <= n; i++){
        c[i] = (c[i - 1] * (n - i) % P) * inv[i] % P;
    }

    //对每个a[i] = 1的位置求出答案
    for(int i = 1; i <= n; i++){
        if(a[i]){
            ans = (ans + (p[i - 1] * p[n - i] % P) * c[i - 1] % P) % P;
        }
    }
    printf("%lld\n", ans);
    return 0;
}

posted on 2024-09-02 11:42  C12AK  阅读(4)  评论(0编辑  收藏  举报