题目是中文的,就不再过多进行赘述了。
这个题有两种思路
- 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;
}
如果上述证明过程有问题,欢迎指正