666RPG(计数dp简单题)

链接:https://ac.nowcoder.com/acm/contest/373/B
来源:牛客网
 

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

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

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

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

答案请对 108+7 108+7取模。

输入描述:

 

输入包含两行。

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

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

输出描述:

输出一行一个整数--符合条件的不同方案数。

示例1

输入

3
-333 -333 -333

输出

1

说明

 

仅一种符合条件的方案

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

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

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

示例2

输入

3
333 333 333

输出

0

示例3

输入

13
518 -643 -503 424 -76 -18 547 26 51 -647 -457 -5 329

输出

2

刚开始拿到这道题的时候,一眼看着是bfs。这是一开始写的代码。

#include<bits/stdc++.h>
using namespace std;
int n,ans=0;
int a[305];
void bfs(int x,int sum)
{
	if(x==n+1){
		if(sum==-666) ans++;
		return;
	}
	if(sum==666) return;
	bfs(x+1,sum+a[x]);
	bfs(x+1,-sum);
}
int main()
{
	while(~scanf("%d",&n))
	{
		ans=0;
		for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
		bfs(1,0);
		printf("%d\n",ans);
	}
 } 

看似很完美,但是毫无疑问的超时了。

 

队友说是dp,我一下就蒙了,这个题如果要dp不是会爆空间,而且还要处理负数的情况(dp只会简单dp和数位dp的zhazha)

最后搜了题解发现是简单的计数dp,其中要还要运用到几个小技巧。

首先是滚动数组,这道题的状态转移方程是这样的:

dp[i][j] += dp[i-1][-j] + dp[i-1][j-ai];表示在第i回合下,分数为j的方案数。直接开二维数组会爆内存,所以要用到滚动数组。可以看出这个状态方程dp[i][j],只和dp[i-1][-j],dp[i-1][j-ai]有关系。也就是说我现在状态只和前面一位的状态有关,再之前的状态就与我没有关系了,换种话说就是我是否可以通过一种方法将我不需要的那些数据用新的数据覆盖掉呢,这样就可以节省大量的空间。得出:dp[i&1][j] + = dp[!(i&1)][-j] + dp[!(i&1)][j-ai];  

其次是j可以为负数,那我就可以设置一个足够大的数,来保证 j加上这个超大数会是一个正数,那么我需要的-666分就相当于maxn-666分:dp[i&1][j+maxn/2] + = dp[!(i&1)][-j+maxn/2] + dp[!(i&1)][j-ai+maxn/2];  

接下来贴上代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 666*300*4+5;
const int mod = 1e8+7;
int dp[2][maxn];
int a[301];
int n;
int main()
{
	cin>>n;
	for(int i=0;i<n;i++) cin>>a[i];
	dp[1][0+maxn/2]=1;
	for(int i=0;i<n;i++){
	for(int j=-maxn/4;j<maxn/4;j++){
	 	if(j==666) continue;
	 	dp[i&1][j+maxn/2] = (dp[i&1][j+maxn/2]+dp[!(i&1)][-j+maxn/2]+dp[!(i&1)][j-a[i]+maxn/2])%mod;
	 }
	 memset(dp[!(i&1)],0,sizeof(dp[!(i&1)]));
	}
	 
	cout<<dp[(n-1)&1][-666+maxn/2];
}

虽然我思路理解了,不够对于一计数dp的一些细节还是不太明白,以后要做一点计数dp的题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值