动态规划之背包dp 入门例题+笔记

动态规划的适用条件

满足

  • 最优子结构(后面的状态可以由之前的最优子状态转移而来)
  • 无后效性(当前的决策对之后没有影响)
  • 子问题重叠(这样可以优化求解时间复杂度)

动态规划实现方法

  • 递推
    直接写几重 \(for\) 循环转移状态
  • 记忆化 \(dfs\)
    逻辑上更直接更好理解,和递推的时间复杂度本质上是一样的

经典例题

  • 最长上升子序列
    放份 \(O(nlogn)\) 代码吧
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 1e5 + 10;
int n, x;
int a[N], b[N], dp[N], dp2[N];
int len, len2;

void solve() {   //每个系统最多拦截多少导弹
    //维护dp数组(存放大小单调不下降的导弹高度
    memset(dp, 0, sizeof(dp));
    len = 0; len2 = 0;
    int ans = 0;
    dp[++len] = a[1]; dp2[++len2] = a[1];
    for(int i = 2; i <= n; i++) {
        if(a[i] <= dp[len]) dp[++len] = a[i];
        else {
            int id = upper_bound(dp + 1, dp + len + 1, a[i], greater<int>()) - dp;
            dp[id] = a[i];
        }

        if(a[i] > dp2[len2]) dp2[++len2] = a[i];
        else {
            int id = lower_bound(dp2 + 1, dp2 + len2 + 1, a[i]) - dp2;
            dp2[id] = a[i];
        }
    } 
    for(int i = 1; i <= n + 1; i++) {
        if(!dp[i]){
            printf("%d\n", i - 1);
            break;
        }
    }
    for(int i = 1; i <= n + 1; i++) {
        if(!dp2[i]){
            printf("%d\n", i - 1);
            break;
        }
    }
}

int main(){
    while(cin >> x) a[++n] = x;
    solve();
    system("pause");
    return 0;
}
  • 最长公共子序列
    \(if \ a[i] = b[i] , dp[i][j] = dp[i-1][j-1]+1\)
    \(else \ dp[i][j] = max(dp[i-1][j],dp[i][j-1])\)

背包dp

将一些物品装入容量优先的背包,要求能装下的物品的最大价值

01背包

每种物品只有一个,状态为选或者不选

//核心代码
int dp[N];  // dp[i]表示容量为i时物品的最大价值
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++) {  //
  for(int j = V; j >= w[i]; j--) {
    dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
  }
}

完全背包

和01背包的区别是每种物品有无限个,每种物品可选任意个或不选

//核心代码
int dp[N];  // dp[i]表示容量为i时物品的最大价值
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i++) {  //
  for(int j = w[i]; j <= V; j++) {
    dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
  }
}

多重背包

每种物品有 \(k_i\) 件,不难看出,可以把它分成 \(k_i\) 件相同的物品,每件选一次

时间复杂度为 \(O(n*W*k_i)\) ,可能出现的问题是会tle

这时需要用到的办法是二进制分组:对 \(k_i\) 进行二进制分组,分成1,2,... \(2^k\) ,以及剩余的不满足 \(2^(k+1)\) 的部分
这样做的好处是能表示所有 \(\in \ [1,k_i]\) 的值,同时复杂度缩小为 \(O(n*W*log_2k_i)\)

code:

int l=1;
while(a[i].num>=l){
  cnt++;
  b[cnt].v=l*a[i].v;
  b[cnt].w=l*a[i].w;
  a[i].num-=l;
  l*=2;
}
//然后当成01背包转移状态

混合背包

是上面三种情况的混合,有的物品能取一次、有的多次,有的 \(k\)

for (循环物品种类) {
  if (是 0 - 1 背包)
    套用 0 - 1 背包代码;
  else if (是完全背包)
    套用完全背包代码;
  else if (是多重背包)
    套用多重背包代码;
}

二维费用背包

在一维费用背包的基础上增加一维,注意数组不应该开存放编号的那一维(因为容易MLE

分组背包

物品分为若干组,每一组中的物品互相冲突(即 每组物品最多选1件)

凸显出冲突关系的方法:对于每一组物品,先循环总重量,再循环该组中的物品

int main(){
	cin >> W >> n;
	for(int i = 1; i <= n; i++) {
		cin >> w[i] >> v[i] >> p[i];
		if(kind.count(p[i]) == 0) {
			kind.insert(p[i]);
		}
		item[p[i]].pb(i);
	}
	for(auto k:kind) {
		for(int j = W; j >= 0; j--) {
			for(int i = 0; i < item[k].size(); i++) {
				if(j < w[item[k][i]]) continue;
				dp[j] = max(dp[j], dp[j - w[item[k][i]]] + v[item[k][i]]);
				ans = max(ans, dp[j]);
			}
		}
	}
	printf("%d\n", ans);
    system("pause");
    return 0;
}

有依赖的背包

代表例题:P1064 [NOIP2006 提高组] 金明的预算方案

分析
因为选附件一定是建立在选主件的基础上,而且每个主件只有0/1/2个附件,因此可以把主件单独拎出来,分类讨论

  1. 只选主件
  2. 选主件+一件附件
  3. 选主件+两件附件

具体见代码

// #pragma GCC optimize(2)
#include<bits/stdc++.h>  
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 63;
int t, n, m;
int zhu[N], v[N], p[N], W;
int dp[100006], ans;
set<int> zj;
vector<int> item[N];
bool vis[N];

int main(){
	cin >> W >> n;
	W = W / 10 * 10;
	for(int i = 1; i <= n; i++) {
		cin >> v[i] >> p[i] >> zhu[i];
		if(zhu[i]) {
			item[zhu[i]].pb(i);
			vis[i] = 1;
		}
	}
	for(int i = 1; i <= n; i++) if(!vis[i]) zj.insert(i);
	for(auto i:zj) {  //主件
		if(item[i].size() == 0) {
			for(int j = W; j >= v[i]; j -= 10) {
				dp[j] = max(dp[j], dp[j - v[i]] + v[i] * p[i]);
				ans = max(ans, dp[j]);
			}
		}

		if(item[i].size() == 1) {
			for(int j = W; j >= v[i]; j -= 10) {
				if(j >= v[i]) dp[j] = max(dp[j], dp[j - v[i]] + v[i] * p[i]);
				if(j >= v[i] + v[item[i][0]]) 
					dp[j] = max(dp[j], dp[j - v[i] - v[item[i][0]]] + v[i] * p[i] + v[item[i][0]] * p[item[i][0]]);
				ans = max(ans, dp[j]);
			}
		}

		if(item[i].size() == 2) {
			for(int j = W; j >= v[i]; j -= 10) {
				if(j >= v[i]) dp[j] = max(dp[j], dp[j - v[i]] + v[i] * p[i]);
				if(j >= v[i] + v[item[i][0]]) 
					dp[j] = max(dp[j], dp[j - v[i] - v[item[i][0]]] + v[i] * p[i] + v[item[i][0]] * p[item[i][0]]);
				if(j >= v[i] + v[item[i][1]]) 
					dp[j] = max(dp[j], dp[j - v[i] - v[item[i][1]]] + v[i] * p[i] + v[item[i][1]] * p[item[i][1]]);
				if(j >= v[i] + v[item[i][1]] + v[item[i][0]]) 
					dp[j] = max(dp[j], dp[j - v[i] - v[item[i][1]] - v[item[i][0]]] + v[i] * p[i] + v[item[i][1]] * p[item[i][1]] + v[item[i][0]] * p[item[i][0]]);
				ans = max(ans, dp[j]);
			}
		}
		// cout<<i<<' '<<ans<< endl;///
	}
	printf("%d\n", ans);
    system("pause");
    return 0;
}

泛化物品背包

例题:P1336 最佳课题选择

// #pragma GCC optimize(2)
#include<bits/stdc++.h>  
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
#define pb push_back
using namespace std;
const int N = 206;
int t, n, m;
int a[22], b[22], W;
ll dp[205], ans;

ll qpow(ll a,ll b){
	ll ans=1;
	while(b){
		if(b%2){
			ans*=a;
		}
		a*=a;
		b/=2;
	}
	return ans;  
}

int main(){
	cin >> W >> n;
	for(int i = 1; i <= n; i++) {
		cin >> a[i] >> b[i];
	}
	for(int i = 0; i <= W; i++) dp[i] = 2e18; 
	ans = 2e18;
	dp[0] = 0;
	for(int i = 1; i <= n; i++) {
		for(int j = W; j >= 0; j--) {
			for(int x = j; x >= 1; x--) {
				dp[j] = min(dp[j], dp[j - x] + 1ll * a[i] * qpow(x, b[i]));
			}
		}
	}
	printf("%lld\n", dp[W]);
    system("pause");
    return 0;
}
posted @ 2022-08-27 13:53  starlightlmy  阅读(70)  评论(0)    收藏  举报