「Day 9 & 10—DP问题」

DP问题

定义

什么是 DP,答曰:一种通过将全局问题分解成不同的子问题来进行对复杂问题的计算。
在我看来就是一种递推的 ProMax 版,依旧是用之前计算过的来推出现在要计算的。

DP板子问题

P1115 最大子段和

思路

我们用 dp 数组来定义到 i 为止,最大的子段和,那么我们在面对 dpi 的时候,应该怎么操作呢。首先什么情况下我们要继续接着前面的选,就是前面的大于 0 的时候,否则我们就可以单开一次,让 dpi=ai 即可。

代码
点击查看代码
#include<iostream>
using namespace std;

const int MAXN = 2 * 1e6 + 5;
int a[MAXN];
int dp[MAXN];
int n,ans = -10086;

int main(){
	
	cin >> n;
	for(int i = 1;i <= n;i ++) cin >> a[i];
	for(int i = 1;i <= n;i ++){
		if(dp[i - 1] >= 0) dp[i] = dp[i - 1] + a[i];
		else{
			dp[i] = a[i];
		}
		ans = max(dp[i],ans);
	}
	cout << ans << "\n";
	return 0;
}

最大子矩阵问题

思路

首先呢这个题没有找到有这个题的 OJ,所以就直接写了。
最大子矩阵这个问题太大了,如果是枚举的话 O(n4) 肯定爆炸,有没有什么稍稍优化一点的方法吗?有。想一想我们刚刚说的最大子段和,如果能将这个二维问题压成一维就好了,那么如何去做呢。可以用一个线性前缀和,sumi,j 表示第 j 列前 i 个元素的前缀和。

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
	    //j表示第j列,剩下的那一维和正常的前缀和没什么大区别。
        pre[i][j] = pre[i - 1][j] + a[i][j];
    }
}

接下来如何求呢,我们既然知道了在第 j 列上的前缀和了,那么例如
3 3
1 -2 3
-4 5 -6
7 -8 9
对于每一列的前缀和为
1 -2 3
-3 3 -3
4 -5 6
例如我们要算上界为 2 和下界为 3 的子矩阵和,也就是最后两行,其每列的和为
3 -3 3
这个该如何算呢?
利用 pre[3][1]pre[21][1] 计算出第一列的,扩展到正常的就是: pre[j][k]pre[i1][k] 其计算的就是第 K 行从第 i 到第 j 的数的和。
计算后再用最大子段和进行计算即可。

// 求解最大子矩阵和
long long ans = -inf;
for (int i = 1; i <= n; i++) { // 枚举上边界
    for (int j = i; j <= n; j++) { // 枚举下边界
        long long sum = 0;
        // 第 k 列,[i,j] 的和为:pre[j][k] - pre[i - 1][k]
        // 所以就转换为最大子段和问题了
        for (int k = 1; k <= m; k++) {
            long long tmp = pre[j][k] - pre[i - 1][k];
            if (sum >= 0) {
                sum += tmp;
            } else {
                sum = tmp;
            }
            ans = max(ans, sum);
        }
    }
}

B3637 最长上升子序列

思路+代码
#include<iostream>
using namespace std;

int n;
int a[10005],dp[10005];

int main(){
	
	cin >> n;
	for(int i = 1;i <= n;i ++){
		cin >> a[i];
	}
	for(int i = 1;i <= n;i ++){
		dp[i] = 1;
		for(int j = 1;j < i;j ++){
			//保证上升
			//如果是最长不下降子序列就把<改为<=即可
			if(a[j] < a[i]){
				//取最大的
				dp[i] = max(dp[i],dp[j] + 1);
			}
		}
	}
	int ans = -1;
	for(int i = 1;i <= n;i ++){
		ans = max(ans,dp[i]);
	}
	cout << ans << "\n";
	return 0;
}

P1439 【模板】最长公共子序列

思路

首先是我们考虑状态,dp[i][j] 表示前 s[i] 和前 t[i] 个字符的最长公共子序列长度,对于一个 dp[i][j] 来说,我们有两种情况,一是 s[i]==t[i] 这个时候让 dp[i][j]=dp[i1][j1]+1 即可,否则就取 s[i] 结尾和 t[i] 结尾的最大值即可。

代码(无优化50pts)
点击查看代码
#include<iostream>
#include<algorithm>
using namespace std;

int n;
int a[200005],b[200005];
int dp[8005][8005];

int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	} 
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			//两种情况
			if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1] + 1;
			else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
		}
	}
	
	cout<<dp[n][n]<<endl;
	return 0;
}

简单背包问题

P1048 [NOIP2005 普及组] 采药

思路

这个题可以说是最正规的 01 背包了,我们设一个状态 dp[i][j] 来表示在面对第 i 个物品时背包容积还剩 j 时的最大价值。
对于第 i 个物品有选和不选两种情况:
dp[i][j]=dp[i1][j] (不选的情况)
dp[i][j]=max(dp[i1][jw[i]]+v[i],dp[i1][j]) (选的情况)

代码
点击查看代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

int dp[21][1010];
int w[21], c[21];
int main() {
    int N, V;
    cin >> N >> V;
    for (int i = 1; i <= N; i++) {
        cin >> w[i] >> c[i];
    }
    for (int i = 1; i <= N; i++) {
        for (int j = 0; j <= V; j++) {
            if (j >= c[i]) {
                dp[i][j]=max(dp[i-1][j-c[i]]+w[i],dp[i-1][j]);
            } else {
                dp[i][j]=dp[i-1][j];
            }
        }
    }
    cout << dp[N][V] << endl;
    return 0;
}

P1757 通天之分组背包

思路

这个题和上个题唯一不同是这个题每个物品有好多件,于是我们就可以在转移的过程中枚举件数 k,有如下转移方程
dp[i][j]=dp[i1][jw[i]k]+c[i]k,dp[i1][j](j>=w[i]k)

代码
点击查看代码
for (int i = 1; i <= N; i++) {
    for (int j = 0; j <= V; j++) {
        for (int k = 0; k <= n[i]; k++) {
            if (j >= c[i] * k) {
                dp[i][j] = max(dp[i - 1][j - c[i] * k] + w[i] * k, dp[i][j]);
            }
        }
    }
}

P1616 疯狂的采药

思路

这个题和上一个的唯一区别是这个题我物品你可以随便取,那么又该如何去做呢?
朴素的方法是这样:

for (int i = 1; i <= N; i++) {
    for (int j = 0; j <= V; j++) {
        for (int k = 0; k * c[i] <= j; k++) {
            dp[i][j] = max(dp[i - 1][j - c[i] * k] + w[i] * k, dp[i][j]);
        }
    }
}

但是啊这破玩意时间复杂度太高了啊,那就优化,如何优化嘞?我们发现

dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+c[i],dp[i1][jw[i]2]c[i]2,...)
然鹅

dp[i][jc[i]]=max(dp[i1][jw[i]]+c[i],dp[i1][jw[i]2]+c[i]2,dp[i1][jw[i]3]+c[i]3,...)
所以

dp[i][j]=max(dp[i1][j],dp[i][jw[i]]+c[i])

代码
for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= v; j++) {
        if (j >= c[i]) {
            dp[i][j] = max(dp[i][j - c[i]] + w[i], dp[i - 1][j]);
        } else {
            dp[i][j] = dp[i - 1][j];
        }
    }
}

简单DP问题

Frog 1

思路

这个题明显是一个构造题,首先构造 dpi 表示跳到 i 为止,最小的花费,那么对于从 1 n 的枚举过程中,显然 dpi=min(dpi1+abs(hihi1),dpi2+abs(hihi2))。知道这个,问题就迎刃而解了。

代码
点击查看代码
#include<iostream>
#include<cmath>
using namespace std;

const int MAXN = 1e5 + 5;
int dp[MAXN];
int h[MAXN];
int n;

int main(){
	cin >> n;
	for(int i = 1;i <= n;i ++){
		cin >> h[i];
	}
	dp[1] = 0;
	dp[2] = abs(h[2] - h[1]);
	for(int i = 3;i <= n;i ++){
		dp[i] = min(dp[i - 1] + abs(h[i] - h[i - 1]),dp[i - 2] + abs(h[i] - h[i - 2]));
	}
	cout << dp[n] << "\n";
	return 0;
}
#### [Frog 2](https://www.luogu.com.cn/problem/AT_dp_b "Frog 2") ##### 思路 这个题和上个没啥大区别,这个可以跳的次数在 1 k 次,于是我们可以枚举,对于 dpi 来说,dpi=min(dpi,dpj+abs(hihj)),不断更新 dpi 的最小值,这个题就没了。
点击查看代码
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;

const int MAXN = 1e5 + 5;
int dp[MAXN];
int h[MAXN];
int n,k;

int main(){
	memset(dp,0x3f3f3f3f,sizeof(dp));
	cin >> n >> k;
	for(int i = 1;i <= n;i ++){
		cin >> h[i];
	}
	dp[1] = 0;
	dp[2] = abs(h[2] - h[1]);
	for(int i = 3;i <= n;i ++){
		for(int j = max(1,i - k);j <= i;j ++){
			dp[i] = min(dp[i],dp[j] + abs(h[i] - h[j]));
		}
	}
	cout << dp[n] << "\n";
	return 0;
}
posted @   To_Carpe_Diem  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示