POJ3093 浅谈背包DP预处理

本文探讨了一种背包问题的预处理方法,该方法通过线性关系递推得到解题所需的条件,从而避免了直接使用动态规划的全部过程。题目要求在不超过背包重量限制的情况下,找出使剩余物品无法放入背包的不同方案数。通过对物品按重量排序并计算组合方案数,实现了高效解决方案,复杂度为O(nm*数据组数)。
摘要由CSDN通过智能技术生成

这里写图片描述
世界真的很大
(配合今天fate新番特地用的贞德2333)
dp的题很多,但是很多时候dp并不是解题的全部,这样的题也见过不少了。这类题大多都是发现解题所需的条件具有线性关系,可以由dp的方式递推得到,作为预处理,这道题就是这样。

Description

给出n个只有重量没有价值的物品和一个大小为m的背包,现在要求选出一些物品放进背包(不能超过重量限制m),使得剩下的物品都不能再放进去,问不同的方案数有多少.如果两个方案中存在一件物品,在一个方案中放进去了,在另一个方案中没有放进去,那么这两个方案不同.1000组数据,n<=30,m<=1000

input

The input begins with a line containing an integer value specifying the number of datasets that follow, N (1 ≤ N ≤ 1000). Each dataset starts with a line containing two integer values V and D representing the number of vendors (1 ≤ V ≤ 30) and the dollar amount to spend (1 ≤ D ≤ 1000) respectively. The two values will be separated by one or more spaces. The remainder of each dataset consists of one or more lines, each containing one or more integer values representing the cost of a margarita for each vendor. There will be a total of V cost values specified. The cost of a margarita is always at least one (1). Input values will be chosen so the result will fit in a 32 bit unsigned integer.

output

For each problem instance, the output will be a single line containing the dataset number, followed by a single space and then the number of combinations for that problem instance.

首先分析一下题意。
剩下的都不能放进盒子里等价于找到一个重量最小的物品,所有比它小的都要在盒子里,比它大的物品把盒子装到不能把它装进去为止的方案数。
于是乎很自然的想到枚举最小的盒子,统计所有的答案。
假设当前枚举不放进去的重量最小的物品的重量为wi,重量比wi小的物品的重量之和为sum,重量比wi大的物品中选出的重量之和为val,那么sum+val < =m,sum+val+wi > m
也就是说,m-sum-wi < val<=m-sum
我们需要求出的就是用比当前枚举的物品重量大的所有物品中,选出几个来凑够val的方案数。好像是具有dp性质的。
我们需要求出”较大的i个物品组合出重量j的方案数”
对物品按照重量排序,定义f[i][j]表示最大的i个物品组合出重量j的方案数
f[i][j]=f[i-1][j]+f[i-1][j-w[i]]
复杂度O(nm*数据组数)
代码:

#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;

int tt,n,m,ans;
int w[55],f[55][1010],sum[55];

bool cmp(const int &a,const int &b)
{
    return a>b;
}
int main()
{
    scanf("%d",&tt);
    for(int e=1;e<=tt;e++)
    {
        ans=0;
        memset(w,0,sizeof(w));
        memset(f,0,sizeof(f));
        memset(sum,0,sizeof(sum));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&w[i]);
        if(w[n]>m)//最小的物品也超过总容量,方案数为0
        {
            printf("%d 0\n",e);
            continue ;
        }
        sort(w+1,w+n+1,cmp);//从大到小排序
        for(int i=n;i>=1;i--)//后缀和  
            sum[i]=sum[i+1]+w[i];
        for(int i=0;i<=n;i++) //从前i个凑出0的方案都是1,只能不取
            f[i][0]=1;
        for(int i=1;i<=n;i++) //DP
            for(int j=w[i];j<=m;j++)
                f[i][j]=f[i-1][j]+f[i-1][j-w[i]];
        for(int i=n;i>=1;i--)
            for(int j=max(m-sum[i+1]-w[i]+1,0);m>=sum[i+1]&&j<=m-sum[i+1];j++)
                ans+=f[i-1][j];
        printf("%d %d\n",e,ans);
    }
    return 0;
}

嗯,就是这样

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 给出一个$n\times m$的矩阵,每个位置上有一个非负整数,代表这个位置的海拔高度。一开始时,有一个人站在其中一个位置上。这个人可以向上、下、左、右四个方向移动,但是只能移动到海拔高度比当前位置低或者相等的位置上。一次移动只能移动一个单位长度。定义一个位置为“山顶”,当且仅当从这个位置开始移动,可以一直走到海拔高度比它低的位置上。请问,这个矩阵中最多有多少个“山顶”? 输入格式 第一行两个整数,分别表示$n$和$m$。 接下来$n$行,每行$m$个整数,表示整个矩阵。 输出格式 输出一个整数,表示最多有多少个“山顶”。 样例输入 4 4 3 2 1 4 2 3 4 3 5 6 7 8 4 5 6 7 样例输出 5 算法1 (递归dp) $O(nm)$ 对于这道题,我们可以使用递归DP来解决,用$f(i,j)$表示以$(i,j)$为起点的路径最大长度,那么最后的答案就是所有$f(i,j)$中的最大值。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码 算法2 (动态规划) $O(nm)$ 动态规划的思路与递归DP类似,只不过转移方程和实现方式有所不同。 状态转移方程如下: $$ f(i,j)=\max f(x,y)+1(x,y)是(i,j)的下一个满足条件的位置 $$ 注意:这里的状态转移方程中的$x,y$是在枚举四个方向时得到的下一个位置,即: - 向上:$(i-1,j)$ - 向下:$(i+1,j)$ - 向左:$(i,j-1)$ - 向右:$(i,j+1)$ 实现过程中需要注意以下几点: - 每个点都需要搜一遍,因此需要用双重for循环来枚举每个起点; - 对于已经搜索过的点,需要用一个数组$vis$来记录,防止重复搜索; - 在进行状态转移时,需要判断移动后的点是否满足条件。 时间复杂度 状态数为$O(nm)$,每个状态转移的时间复杂度为$O(1)$,因此总时间复杂度为$O(nm)$。 参考文献 C++ 代码

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值