第七届传智杯省赛第一场 F.小苯的糖果游戏

F.小苯的糖果游戏

题目描述

小苯和格格正在玩一款名为“吃糖果”的游戏,游戏的过程是这样的:

两人面前有 n n n 堆糖果,从左到右编号从 1 到 n n n,其中第 i i i 堆糖果中有 a i a_i ai 颗糖果。初始时小苯在 0 号位置(第一堆糖果的左侧),格格在 n + 1 n+1 n+1 号位置。(第 n n n 堆糖果的右侧)

假设小苯目前位于 i i i 号糖果堆的位置,格格位于 j j j 号糖果堆( i < j − 1 i < j-1 i<j1),则每秒钟都会发生如下过程:

  • 如果 i = j − 1 i = j - 1 i=j1(即两人已经相邻),则游戏结束。
  • 否则,两人轮流操作,小苯先手:
    • 轮到小苯时,小苯可以选择待在原地;或跳跃到 i + 1 i+1 i+1 号糖果堆,然后选择吃掉所有 a i + 1 a_{i+1} ai+1 颗糖果,或者不吃。
    • 轮到格格时,格格可以选择待在原地;或跳跃到 j − 1 j-1 j1 号糖果堆,然后选择吃掉所有 a j − 1 a_{j-1} aj1 颗糖果,或者不吃。

(当然,吃掉某堆糖果的前提是此堆糖果之前没被吃掉。换句话说每堆糖果被任何人吃掉后都会消失。)

现在我们考虑所有结束的游戏,为了公平起见,小苯希望自己和格格吃掉的糖果数量相同,他想知道有多少种不同的游戏过程能满足他的要求,请你帮他算一算吧。

(游戏过程的不同性在下方备注处给出了定义。)

输入描述:

本题含有多组测试数据。
第一行一个正整数 T   ( 1 ≤ T ≤ 100 ) T\ (1 \leq T \leq 100) T (1T100),表示测试数据的组数。
接下来对于每组测试数据,输入包含两行。
第一行一个正整数 n   ( 1 ≤ n ≤ 100 ) n\ (1 \leq n \leq 100) n (1n100),表示糖果的总堆数。
第二行 n n n 个正整数 a i   ( 1 ≤ a i ≤ 100 ) a_i\ (1 \leq a_i \leq 100) ai (1ai100),表示每堆糖果的总个数。

输出描述:

对于每组测试数据,输出一行一个整数,表示不同的游戏过程数。
(由于结果可能很大,因此你只要输出答案对 998244353 998244353 998244353 取模的值即可。)

示例1

输入


2 3 1 2 1 6 1 3 1 1 2 1

输出


6 41

提示

说明
考虑第一组测试数据,最终结束时满足小苯要求的,不同的游戏过程有:
小苯位于 i = 0 i=0 i=0,格格位于 j = 1 j=1 j=1,两人都没有吃糖果。
小苯位于 i = 1 i=1 i=1,格格位于 j = 2 j=2 j=2,两人都没有吃糖果。
小苯位于 i = 2 i=2 i=2,格格位于 j = 3 j=3 j=3,两人都没有吃糖果。
小苯位于 i = 3 i=3 i=3,格格位于 j = 4 j=4 j=4,两人都没有吃糖果。
小苯位于 i = 1 i=1 i=1,格格位于 j = 2 j=2 j=2,小苯选择吃掉的糖果编号集合为: { 1 } \{1\} {1},格格选择吃掉的糖果编号集合为 { 3 } \{3\} {3}
小苯位于 i = 2 i=2 i=2,格格位于 j = 3 j=3 j=3,小苯选择吃掉的糖果编号集合为: { 1 } \{1\} {1},格格选择吃掉的糖果编号集合为 { 3 } \{3\} {3}

备注:
考虑两个游戏过程:
游戏过程 1: 结束时,小苯位于 p o s 1 pos_1 pos1,小苯选择拿走的糖果堆的编号集合为 S 1 S_1 S1,格格选择拿走的糖果堆编号集合为 S 2 S_2 S2
游戏过程 2: 结束时,小苯位于 p o s 2 pos_2 pos2,格格位于 p o s 2 pos_2 pos2,小苯选择拿走的糖果堆的编号集合为 S 3 S_3 S3,格格选择拿走的糖果堆编号集合为 S 4 S_4 S4
定义两个游戏过程相同,当且仅当: p o s 1 = p o s 2 pos_1=pos_2 pos1=pos2,同时 S 1 = S 3 S_1=S_3 S1=S3 S 2 = S 4 S_2=S4 S2=S4
否则两个游戏过程不相同。

示例代码

const int N = 1e2 + 5;
const int NN = 1e4 + 5;
const int M = 998244353;

int a[N], f1[N][NN], f2[N][NN];

void solve()
{
    int n; cin >> n;
    int s = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        s += a[i];
    }

    memset(f1, 0, sizeof(f1));
    memset(f2, 0, sizeof(f2));
    
    f1[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {// 遍历堆
        for (int j = 0; j <= s; j++)
        {
            f1[i][j] = f1[i - 1][j]; // 不选第 i 堆糖果
            if (j >= a[i]) 
            {
                f1[i][j] = (f1[i][j] + f1[i - 1][j - a[i]]) % M;
            }
        }
    }

    // f2为后缀dp
    f2[n + 1][0] = 1;
    for (int i = n; i >= 1; i--)
    {
        for (int j = 0; j <= s; j++)
        {
            f2[i][j] = f2[i + 1][j];
            if (j >= a[i])
            {
                f2[i][j] = (f2[i][j] + f2[i + 1][j - a[i]]) % M;
            }
        }
    }
    int ans = 0;
    for (int i = 0; i <= n; i++)
    {
        for (int j = 0; j <= s; j++)
        {
            ans = (ans + f1[i][j] * f2[i + 1][j] % M) % M;
        }
    }
    cout << ans << endl;
}

其实我们是能够将 f 2 f2 f2的优化为滚动数组的,因为我们在算答案的时候只需要知道当前层的信息即可

    f2[0] = 1; // 注意此处数组底标的意义
    for (int i = n; i >= 1; i--)
    {
        for (int j = s; j >= a[i]; j--)
        {
            f2[j] = (f2[j] + f2[j - a[i]]) % M;
        }
        for (int j = 0; j <= s; j++)
        {
            ans = (ans + f1[i - 1][j] * f2[j] % M) % M;
        }
    }
    
    ans = (ans + f1[n][0]) % M; // 一定要加上这一行,上面的循环是没有处理格格在原地的情况的,实际上就是 +1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值