小张的困惑 烟大校赛 01背包变式

题目链接

 题目是中文的,就不再过多进行赘述了。

 这个题有两种思路

  • 1、带剪枝的暴力搜索,就是我利用DFS去暴力搜索所有的选择,从中选出一个满足条件的数,剪枝则是采用是否超过N如果超过就比较完返回,其次就是由于序列1 3 4 和 4 3 1和 3 1 4是一样的,所以我们只需要找出所有单调递增的数列组合就可以了,所以每次递归的起点都是上一次递归的值 + 1,这里对于DFS不进行过多赘述

code

#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <set>
#include <queue>
#include <stack>
#include <deque>
#include <map>
#include <ext/rope>
#include <algorithm>
 
using namespace std;
 
const int INF = 1e9 + 7;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
 
int ans = INF;
int z[25],vis[25];
 
void DFS(int n,int m,int cnt,int res,int bag)
{
    if(res >= n){
        if(ans >= res) ans = res;
        return ;
    }
    if(cnt >= m) return ;
    for(int i = bag; i <= m; i++){
        if(!vis[i]){
            vis[i] = 1;
            DFS(n,m,cnt + 1,res + z[i],i);
            vis[i] = 0;
        }
    }
}
 
int main()
{
    int n,m;
    cin>>m>>n;
    for(int i = 1; i<= m; i++){
        scanf("%d",z +i);
    }
    DFS(n,m,1,0,1);
    cout<<ans<<endl;
    return 0;
}
  • 2、其实我感觉这种方法算是正解,可以算作是一种01背包的变式,可惜比赛的时候没有做出来,看师哥代码刚开始也没有看懂,后来自己在纸上把样例写了一遍,顿然醒悟。

首先我们先说一下dp状态

dp[m,n] 为第m块砖时 >= n 的最低高度
由此我们先写一下状态转移方程,一会在下面会给出解释

dp[i,j] = min(dp[i - 1,j],dp[i - 1,j - a[i]] + a[i])
(二维数组可以优化为一维数组就是 dp[j] = min(dp[j],dp[j - a[i]] + a[i]))

	这里解释一下状态转移方程,由于dp[j]是表示 >= n 的最小高度,,所以
dp[j]中的值是一定 >= j的,所以 dp[j - a[i]] + a[i] 一定是 >= j的长度这样
得到的dp[j - a[i]] + a[i] 一定 > j 
证明:
	dp[j - a[i]] >  j - a[i] 
	两端 + a[i] ---> dp[j - a[i]] + a[i] > j
这个证明就保证了01背包解这个题的正确性
取其中最小即可,也就是我取这块砖能不能
使砖块总高更趋于j,如果可以就取,不能就不拿

上面是状态转移方程,但是这个题在给dp状态赋初值的时候要这样进行赋初值

for(int i = 1; i <= n; i++) dp[i] = sum;(sum为所有砖块的高度)
也就是可以这样理解dp状态,当sum < N时dp[1 -- N]是>=1 --- sum的最低长度

因为砖块的总高度就是上界,就相当于如果这个状态我组合不出来,则这个状态的值就是INF但是由于考虑到砖的总高度可能没有给出的N高
所以直接赋值sum的话就不用考虑这周情况了,因为dp[N]是不会更新,所以最后会直接输
出dp[N]里的sum
证明:
	对于任意一个dp[i] 都有dp[i] >= i,已知的前提条件是 sum < N,那么对于任意
一个dp[i] 都是 >= i && <= sum的
	所以对于任意的dp[j-a[i]]>=j两端+a[i]可得dp[j-a[i]]+a[i]>=j
	由于之前赋初值的原因导致dp[N-a[i]] 不是 >= N - a[i]&& < sum 就是 == sum
	如果是第一种情况,就有dp[N-a[i]] + a[i]>= N>sum故dp[N]会取较小的sum
	因为这些砖最多就sum高,所以最高就取sum
	反之 sum + a[i] 一定 > sum 所以dp[N] 取两者最小为sum
结合上面我们可以知道,由于sum<N所以我们是不可能组合出来高度为N的序列的,所以
就直接输出INF也就是sum,同时这种情况下的输出也满足的了题目要求 
上面就证明了这种方法是完全正确的
 for(int i = 1; i <= m; i++){
        for(int j = n; j >= a[i]; j--){
            dp[j] = min(dp[j],dp[j - a[i]] + a[i]);
        }
        //因为是求 >= j 的最小高度,所以对于 <= a[i]的数字,直接赋值,如果之前赋
        //过值,就比较谁比j差的小,这样也保证了,后面所有dp[j]的值都是 >= j的
        //从而保证了背包的正确性
        for(int j = 1; j <= a[i]; j++) dp[j] = min(dp[j],a[i]);
    }

code

#include <cstdio>
#include <iostream>
#include <cstdlib>

using namespace std;

const int maxn = 2e7 + 5;

int dp[maxn];
int a[26];

int main()
{
    int n,m;
    cin>>m>>n;
    int sum = 0;
    for(int i = 1; i <= m; i++){
        cin>>a[i];
        sum += a[i];
    }
    for(int i = 1; i <= n; i++) dp[i] = sum;
    for(int i = 1; i <= m; i++){
        for(int j = n; j >= a[i]; j--){
            dp[j] = min(dp[j],dp[j - a[i]] + a[i]);
        }
        for(int j = 1; j <= a[i]; j++) dp[j] = min(dp[j],a[i]);
    }
    cout<<dp[n]<<endl;
    return 0;
}

如果上述证明过程有问题,欢迎指正

猜你喜欢

转载自blog.csdn.net/CUCUC1/article/details/109728321