【多校第一场】【dp】 HDU 5291 Candy Distribution

这道题是个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;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值