DP - hdu5291 Candy Distribution

题目:

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>



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值