这道题是个dp。。。是个dp。。。个dp。。。dp。。。dp。。。p。。。。。。
题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=5291
题意就是有n种糖果每种ai个,要把糖分给两个人(可以剩),请问有多少种选糖方法。
首先,我们可以得出一个基本的dp样式:保存当前这种糖果被选取之后两人糖果的差值,最后取出差值为0的就是最后答案。(然而只知道这个并没有什么琴梨鸟用)
其实这题思路倒是很好懂,但是在更新那里就是个坑了。因为糖最多200种,然后每种糖最多200个,那么每一个点的更新应该最多有200种可能性。。。然后有两百层每层有200*200=40000中差值可能性。。。我要报警了QAQ
那么我们现在要优化。。。根据某个看起来很不靠谱其实也很不靠谱的学长所说更新的时候只要预处理一下就可以用O(1)的复杂度去处理。情况如下
ai为奇数时(以3为例)
上一层 dp[k], dp[k+1], dp[k+2], dp[k+3], dp[k+4], dp[k+5] ,dp[k+6], dp[k+7]....
当前层更新dp[k+3]时的系数 1 1 2 2 2 1 1 0
当前层更新dp[k+4]时的系数 0 1 1 2 2 2 1 1
。。。就是这样。。。改变的系数是有规律的。。。(感觉说明很麻烦所以直接画出来比较好)
偶数时也有规律,然后总结一下就出来了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 40200
using namespace std;
const long long mod=1e9+7;
int n;
long long dp[2][80500];
long long sum_odd[80500];
long long sum_even[80500]; //写完之后发现其实不需要开两个sum数组的我眼泪掉下来QAQ
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
memset(sum_even,0,sizeof sum_even);
memset(sum_odd,0,sizeof sum_odd);
memset(dp[0],0,sizeof dp[0]);
int side=0;
dp[0][N]=1;
int now=0;
for(int i=1;i<=n;i++)
{
now=1-now;
memset(dp[now],0,sizeof dp[now]);
int x;
scanf("%d",&x);
side+=x;
for(int j=N-side;j<=N+side;j++)
{
if((j+x)%2) sum_odd[j+x]=((sum_odd[j+x-2]+dp[1-now][j+x])%mod);
else sum_even[j+x]=((sum_even[j+x-2]+dp[1-now][j+x])%mod);
if(x%2)
{
if((j+x)%2)
{
dp[now][j]=((dp[now][j-1]+(sum_odd[j+x]-sum_odd[j-1])-(sum_even[j-2]-sum_even[j-x-3])+10*mod)%mod); //10*mod其实是防负数的。。。最开始忘记写导致一直WA
}
else
{
dp[now][j]=((dp[now][j-1]+(sum_even[j+x]-sum_even[j-1])-(sum_odd[j-2]-sum_odd[j-x-3])+10*mod)%mod);
}
}
else
{
if((j+x)%2)
{
dp[now][j]=((dp[now][j-1]+(sum_odd[j+x]-sum_odd[j-2])-(sum_even[j-1]-sum_even[j-x-3])+10*mod)%mod);
}
else
{
dp[now][j]=((dp[now][j-1]+(sum_even[j+x]-sum_even[j-2])-(sum_odd[j-1]-sum_odd[j-x-3])+10*mod)%mod);
}
}
}
//printf("%lld\n",dp[now][N]);
}
long long ans=dp[now][N];
printf("%lld\n",ans);
}
return 0;
}