NKOJ 3792 分糖果(差值dp+前缀和优化)

P3792分糖果

问题描述

有n种糖果(编号1到n),第i号糖果有Ai颗,现需要将所有糖果分给两个小朋友,要求两个小朋友得到糖果数量相等,问有多少种分法?
(可以不必将所有糖果分完。如全部都不分,每人的糖果数量为0,也算是一种分法)

输入格式

第一行,一个整数n,表示糖果种类数量
第二行,n个空格间隔的整数,表示每种糖果的数量

输出格式

一行,一个整数,表示总的方案数,答案 mod 10^9+7后再输出。

样例输入 1

1
2

样例输出 1

2

样例输入 2

2
1 2

样例输出 2

4

提示

1<=n<=200
1<=Ai<=200


容易想到令 F[i][j][k] 表示分完第 i 中糖后,第一个小朋友有 j 颗糖,第二个小朋友有 k 颗糖的方案数。
再想发现,我们只关心他们分的糖是否相等,因此可以简化成令 F[i][j] 表示分完第 i 中糖,两人分的糖果的差值为 j
那么, F[i][j]=F[i1][jk]((A[i]k)/2+1) ,这个递推式的复杂度是 O(n4) ,我们需要优化
考虑 F[i][j]F[i][j1] ,讨论 A[i] 的奇偶,观察可以发现 F[i][j] 关于 F[i][j1] 的递推式,这个式子可以前缀和优化。于是复杂度就降到了 O(n3) ,鉴于递推式比较冗长,这里就不写了。
实际做的时候只需要取比较小的 A[i] F[i][j]F[i][j1] 展开,观察一下就可以发现关于奇偶性的规律。
代码比较恶心,前缀和需要根据奇偶分开。


代码:

#include
#include
#include
#define ll long long
#define N 123456
using namespace std;
ll F[N],S[2][N],A[N],n,m,p,mod=1e9+7;
int main()
{
    ll i,j,k,t;
    scanf("%lld",&n);
    for(i=1;i<=n;i++)scanf("%lld",&A[i]),m+=A[i];
    F[m]=1;p=m*2;
    for(i=1;i<=n;i++)
    {
        S[0][0]=F[0];
        S[1][0]=0;
        for(j=1;j<=p;j++)//先处理出前缀和
        {
            k=j&1;
            S[k][j]=(S[k][j-1]+F[j])%mod;
            S[k^1][j]=S[k^1][j-1];
        }
        for(j=0;j<=A[i];j++)F[0]=(F[0]+F[j]*((A[i]-j>>1)+1))%mod;//先算出F[0]
        for(j=1;j<=p;j++)//递推
        {
            k=(A[i]+j)&1;//判断需要的前缀和数组的奇偶
            F[j]=(F[j-1]+S[k][j+A[i]]-S[k][j-1])%mod;
            if(j-A[i]-2<0)t=0;
            else t=S[k^1][j-A[i]-2];
            F[j]=(F[j]-S[k^1][j-1]+t)%mod;
        }
    }
    cout<<(F[m]+mod)%mod;
}

你可能感兴趣的:(动态规划)