『HDU 5800』To My Girlfriend

转载声明:http://blog.csdn.net/overload1997/article/details/52127995
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5800

题意:
这里写图片描述

就是给你 n个数 其中**(序号i,j)是必选的,(序号k,l)是必不选的.使得其中的子集在n个数中的总权值**为m.
例如样例
f(1,2,3,4)=1 f(2,1,3,4)=1 f(1,3,2,4)=1 f(3,1,2,4)=1
f(1,2,4,3)=1 f(2,1,4,3)=1 f(1,3,4,2)=1 f(3,1,4,2)=1 所以答案为8.

个人感想:
看了一下题目,我基本没什么思路,然后喵了一下题解,说是dp,然后我想了一个晚上…我觉得自己真是弱鸡,还是没怎么想到,看了网上题解,可以推出朴素的O(n^3)算法,我尼玛我一点感觉也没,我都不知道怎么推出朴素了…我反而悟出了这道题的前2维的计数,但是我就想不到怎么把必选不选的排掉.很烦躁.
然后还是看了一下转载了.但是我就想不通最后两维,好艰难啊.
今天早上还是琢磨了一下,我才把它弄懂.

se->select.
dp[i][j][se][nse] 代表 前i个数,和为j,有se个数必选,有nse个必不选的方案数.
是不是很凌乱.我们一步步来,先想前2维.我到不知道为什么别人数是个背包,千万别和背包混一起,我感觉会走弯路,不过确实也类似了.

我的开始是这样想的.
首先 dp[i][j].前i个数和为j,对于当前这个数,如果选择了,就得**+dp[i-1][j-a[i]],这时如果不选,+dp[i-1][j].这个先想明白.
然后再想到
第3维**(第4维也是类似).
dp[i][j][1]+=dp[i-1][j][1] ,首先前**[i-1]个数和为j的方案数,而且其中有1个是必选了,但没有选a[i].
dp[i][j][1]+=dp[i-1][j-a[i]][1],首先前
[i-1]个数和为j的方案数,而且其中有1个是必选了,但选了a[i].a[i],不是必选的.
dp[i][j][1]+=dp[i-1][j-a[i]][0],首先前
[i-1]个数和为j的方案数,而且其中有1个是必选了,选了a[i].a[i]是必选的.
这样理解应该懂了吧,其中1维也是这样的思想…这就推出来了几种
必选必不选**的方案数了.

分析: 计数dp.
代码:

/* Author:GavinjouElephant
 * Title:
 * Number:
 * main meanning:
 *
 *
 *
 */

//#define OUT
#include <iostream>
using namespace std;
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <sstream>
#include <cctype>
#include <vector>
#include <set>
#include <cstdlib>
#include <map>
#include <queue>
//#include<initializer_list>
//#include <windows.h>
//#include <fstream>
//#include <conio.h>
#define MaxN 0x7fffffff
#define MinN -0x7fffffff
#define Clear(x) memset(x,0,sizeof(x))
typedef int ll;
const int INF=0x3f3f3f3f;
int T;
int n,s;
const ll mod=1e9+7;
ll a[1005];
ll dp[1005][1005][3][3];

int gmul(int a,int n)
{
    int res=0;
    while(n)
    {
        if(n&1){res=(res+a)%mod;}
        a=(a+a)%mod;
        n>>=1;
    }
    return res%mod;

}
int main()
{
#ifdef OUT
    freopen("coco.txt","r",stdin);
    freopen("lala.txt","w",stdout);
#endif
    scanf("%d",&T);
    while(T--)
    {
       memset(dp,0,sizeof(dp));
       scanf("%d%d",&n,&s);
       for(int i=1;i<=n;i++)scanf("%d",&a[i]);

       dp[0][0][0][0]=1;
       for(int i=1;i<=n;i++)
       {
           for(int j=0;j<=s;j++)
           {
              for(int se=0;se<=2;se++)
              {
                  for(int nse=0;nse<=2;nse++)
                  {
                      dp[i][j][se][nse]=(dp[i][j][se][nse]+dp[i-1][j][se][nse])%mod;
                      if(j>=a[i])
                      {
                          dp[i][j][se][nse]=(dp[i][j][se][nse]+dp[i-1][j-a[i]][se][nse])%mod;
                          if(se)
                          {
                              dp[i][j][se][nse]=(dp[i][j][se][nse]+dp[i-1][j-a[i]][se-1][nse])%mod;
                          }
                      }
                      if(nse)
                      {
                          dp[i][j][se][nse]=(dp[i][j][se][nse]+dp[i-1][j][se][nse-1])%mod;
                      }
                  }
              }
           }
       }
       long long ans=0;
       for(int j=1;j<=s;j++)
       {
           ans=(ans+(long long)dp[n][j][2][2])%mod;
       }
       printf("%d\n",gmul(ans,4));

    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值