题目:
http://acm.hdu.edu.cn/showproblem.php?pid=5291
题意:
给若干种糖果,每种糖果数量不一定相等,现需要将所有糖果分成两份,要求两份糖果数量相等,问有多少种分法?【可以不必将所有糖果分完,如全部不分,每份糖果数量为0,也是一种分法】
思路:
求有多少分法的题目一般都是dp……
dp[i][j]:只使用前i种糖果的情况下,第一份比第二份多j个糖果的分法总数,j的值可能为负数,处理时应加上一个常数使j恒为正数
朴素的dp转移方程:
dp[i][j+x] += dp[i-1][j]
其中x的取值是-arr[i] ~ arr[i],arr[i]为第i种糖果的数量,表示只用第i种糖果,第一份可以比第二份多x个
这样解时间复杂度是O(n^4),n=200,分别为n种糖果,n^2的糖果数量(j的取值应为所有糖果的总数*2),转移时每个状态可以转移到2*n个状态,所以共O(n^4)
显然前三个n是没法优化的,考虑能否把状态转移的时间复杂度降为O(1)
思考每个状态dp[i-1][j]对dp[i][...]的贡献,dp[i][j]=dp[i-1][j-x]*((arr[i]-x)/2+1) + dp[i-1][j-(x-1)]*((arr[i]-(x-1)/2+1)) + ... + dp[i-1][j]*(arr[i]/2+1) + ... + dp[i-1][j+x]*((arr[i]-x)/2+1)
可以看到dp[i][j]是dp[i-1][j-x] ~ dp[i-1][j+x]乘上对应权值之和,而权值序列是一个按奇偶序列分布的等差数列
若arr[i] = 3,权值序列为1 1 2 2 2 1 1
若arr[i] = 2,权值序列为1 1 2 1 1
观察找到规律,通过sum数组预处理,即可在O(1)的时间复杂度下完成状态转移
代码:
<span style="font-family:Courier New;">#include<stdio.h>
#include<iostream>
#include<string>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<functional>
#pragma comment(linker, "/STACK:102400000,102400000")//C++
using namespace std;
const double PI = 3.141592653589793238462643383279502884197169399;
const int MAXINT = 0x7fffffff;
const int MAXSIZE = 200+30;
const int MOD = 1e9 + 7;
int main(){
int total;
int n;
long long dp[2*MAXSIZE*MAXSIZE];
int arr[MAXSIZE];
long long sum[2*MAXSIZE*MAXSIZE];
const int banl = MAXSIZE*MAXSIZE;
int candy=0;
cin>>total;
while (total--){
scanf("%d",&n);
memset(arr,0,sizeof(arr));
memset(dp,0,sizeof(dp));
candy = 0;
for (int i=0;i<n;++i){
scanf("%d",arr+i);
candy+=arr[i];
}
dp[banl] = 1;
for (int i=0;i<n;++i){
memset(sum,0,sizeof(sum));
if (arr[i]%2){
for (int j=-candy + banl - arr[i]; j <= -candy + banl;j+=2)
sum[-candy + banl - 1] = sum[-candy + banl -1] + dp[j];
for (int j=-candy + banl - arr[i] + 1; j<= -candy + banl + 1; j+=2)
sum[-candy + banl] = sum[-candy + banl] + dp[j];
for (int j=-candy + banl + 1;j<=candy + banl + arr[i]; ++j){
sum[j] = sum[j-2] - dp[j-2-arr[i]+1] + dp[j];
}
}
else {
for (int j=-candy + banl - arr[i]; j <= -candy + banl;j+=2)
sum[-candy + banl] = sum[-candy + banl] + dp[j];
for (int j=-candy + banl - arr[i] + 1; j<= -candy + banl + 1; j+=2)
sum[-candy + banl + 1] = sum[-candy + banl + 1] + dp[j];
for (int j=-candy + banl + 2;j<=candy + banl + arr[i]; ++j){
sum[j] = sum[j-2] - dp[j-arr[i]-2] + dp[j];
}
}
dp[-candy + banl] = (dp[-candy + banl] * (arr[i]/2+1)) % MOD;
for (int j=1;j<=arr[i];++j)
dp[-candy + banl] = (dp[-candy + banl] + dp[-candy + banl + j] * ((arr[i] - j)/2+1)) % MOD;
if (arr[i]%2){
for (int j=-candy + banl + 1;j<=candy + banl; ++j)
dp[j] = (dp[j-1] - sum[j-2] + sum[j+arr[i]]) % MOD;
}
else{
for (int j=-candy + banl + 1;j<=candy+ banl; ++j)
dp[j] = (dp[j-1] - sum[j-1] + sum[j+arr[i]]) % MOD;
}
//for (int k=-candy;k<=candy;++k){
// cout<<"dp["<<i<<"]["<<k<<"]: "<<dp[banl+k]<<endl;
//}
}
//for (int k=-candy;k<=candy;++k){
// cout<<"dp["<<n<<"]["<<k<<"]: "<<dp[banl+k]<<endl;
//}
int res = dp[banl];
res %= MOD;
res = (res+MOD)%MOD;
cout<<res<<endl;
}
return 0;
}</span>