计数DP,牛客练习赛41B题: 666RPG

传送门

lililalala正在玩一种有 N个回合的回合制RPG游戏,初始分数为0,第 i i个回合lililalala有如下两种选择。

A.将分数加上 ai ai
B.将分数 ×-1 ×-1

lililalala同样也很讨厌野兽数 666 ,但是他很却喜欢数字 -666 -。他想知道有多少种不同的方案使得N个回合后分数变为 -666且在任何一个回合之后分数都不为 666。

如果两种方案有任何一个回合选择不同,就认为这两种方案是不同的。

答案请对 10^8+7 取模。

输入描述:

输入包含两行。

第一行一个整数 N(1≤N≤300) N(1≤N≤300)。

第二行 N N个整数 a1a2a3…an(-666≤ a1a2a3…an≤666) a1a2a3…an(-666≤ a1a2a3…an≤666)。

输出描述:
输出一行一个整数–符合条件的不同方案数。

输入
3
-333 -333 -333

输出
1
说明

仅一种符合条件的方案

第一回合选择将分数×−1。分数为0

第二回合选择将分数加上-333。分数为 -333

第三回合选择将分数加上 -333。分数为 -666

输入
3
333 333 333
输出
0

输入
13
518 -643 -503 424 -76 -18 547 26 51 -647 -457 -5 329
输出
2

计数dp.
dp[i][j]代表第i个回合后分数为j的方案数
则:dp[i][j]=dp[i-1][j-ai]+dp[i-1][-j]
因为开二维内存不够,需要使用滚动数组, 同时, 避免负数的影响用dp[i][j+add] 来代表第i个回合后分数为j的方案数。因为j最小为300 * -666 这里取add = 300 * 666。
特判屏蔽所有来自j=666的状态转移

计数dp不太熟悉, 卡了很久在 dp[cnt][j+add]=dp[cnt^1] [j-a[i]+add]+dp[cnt^1][-j+add];
之前是分开写的用了+= 但这里用到了滚动数组, 前面一轮算的数会保留到这一轮, 用+= 会造成错误。


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<functional>
using namespace std;

const int add = 300*666, mod = 1e8 + 7;
int a[310];
int dp[2][add * 2];				//j的范围由300 * -666 到 300 * 666 改为 0 ~ 300 * 666 * 2

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

     dp[0][add] = 1;				// 第0回合取0值得方案=数为1
     int sum = 0;
    for(int i = 1; i <= n; i++)
    {
        sum += abs(a[i]);
        int cnt = i % 2;

        for(int j = -1 * sum; j <= sum; j++)						//遍历从-sum到sum, 避免每次从极大到极小遍历
        {
            if(j == 666) dp[cnt][j + add] = 0;
            else
            {
                dp[cnt][j+add]=dp[cnt^1][j-a[i]+add]+dp[cnt^1][-j+add];			//状态转移:dp[i][j]=dp[i-1][j-ai]+dp[i-1][-j]
                dp[cnt][j + add] %= mod;															//保存答案时避免负数的影响, 加add
            }
        }
    }

    printf("%d", dp[n % 2][add - 666]);
}

猜你喜欢

转载自blog.csdn.net/qq_40212930/article/details/88076040
今日推荐