题目描述

There are N different kinds of transport ships on the port. The i^th kind of ship can carry the weight of V[i] and the number of the ith kind of ship is 2C[i]-1. How many different schemes there are if you want to use these ships to transport cargo with a total weight of S? It is required that each ship must be full-filled. Two schemes are considered to be the same if they use the same kinds of ships and the same number for each kind.

 

输入

The first line contains an integer T(1≤T≤20), which is the number of test cases. 
For each test case: 
The first line contains two integers: N(1≤N≤20), Q(1≤Q≤10000), representing the number of kinds of ships and the number of queries.
For the next N lines, each line contains two integers: V[i] (1≤V[i]≤20), C[i] (1≤C[i]≤20), representing the weight the i^th kind of ship can carry, and the number of the i^th kind of ship is 2C[i]-1.
For the next Q lines, each line contains a single integer: S (1≤S≤10000), representing the queried weight.

 

 

输出

For each query, output one line containing a single integer which represents the number of schemes for arranging ships. Since the answer may be very large, output the answer modulo 1000000007.

 

样例输入

复制样例数据

1
1 2
2 1
1
2

样例输出

0
1

题目大意:有n个物品,每个物品的重量是v[i],数量有2^c[i]-1个,询问q次,每次询问有多少种组合能组成 数x

题目思路:

首先都可以想到朴素的思路,每一种物品能组成的种类数目是可以确定的,确定之后直接跑,但是那样跑背包dp会重复,例如 有一类物品,价值为2,有3个 ,所以 他能组成 2 4 6,跑2,2是一种,跑4,4是一种,6是一种,跑6,6自己也是一种,所以会造成重复.所以我们优化一下,从上个例子来也不难看出.2 4 是可以构成6的,为什么呢,这里有一个性质,把一个数拆分,小于该数的二进制数一定能够成该范围内的所有数,举个例子,假设 n=15:

那么 把n分成  1 2 4 8 ,3=1+2,4=1+3,6=2+4,7=1+2+4.

这样我们遍历 分成的二进制数就可以把所有的种类都遍历一遍且没有重复.注意dp的时候,我们从最大值到最小值反向DP,这样比较简单,否则正着dp需要判断很多条件.注意初始状态为 dp[0]=1即可.

AC代码:

#include <bits/stdc++.h>
#define E 2.718
using namespace std;
typedef  long long ll;
const ll INF=0x7f7f7f7f;
const int maxn=1e6+8;
const double eps=1e-10;
const ll mod=1000000007;
ll n,m,f;
ll v[50],c[50];
ll dp[10005];
ll save[500];
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        memset(dp,0,sizeof(dp));
        ll cnt=0;
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%lld%lld",&v[i],&c[i]);
        for(int i=1;i<=n;i++)
            for(int k=0;k<c[i];k++)
                save[++cnt]=v[i]<<k;
        dp[0]=1;
        for(int i=1;i<=cnt;i++)
        {
            for(int k=10001;k>=save[i];k--)
            {
                dp[k]+=dp[k-save[i]];
                dp[k]%=mod;
            }
        }
        while(m--)
        {
            ll x;scanf("%lld",&x);
            printf("%lld\n",dp[x]);
        }
    }
    return 0;
}

总结:其实这种二进制优化,之前训练中讲过的,竟然忘了......,所以对于每一个知识点都需要练习.

1.以后遇到类似这种多重背包的问题,如果拆分数据很大,就采用二进制优化.

2.反向dp的使用,(可以优化代码量)