「Day 9 & 10—DP问题」

DP问题

定义

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

DP板子问题

P1115 最大子段和

思路

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

代码
点击查看代码
#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(n^4)\) 肯定爆炸,有没有什么稍稍优化一点的方法吗?有。想一想我们刚刚说的最大子段和,如果能将这个二维问题压成一维就好了,那么如何去做呢。可以用一个线性前缀和,\(sum_i,{_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[2 - 1][1]\) 计算出第一列的,扩展到正常的就是: \(pre[j][k] - pre[i - 1][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[i - 1][j - 1] + 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[i - 1][j]\) (不选的情况)
\(dp[i][j] = max(dp[i - 1][j - w[i]] + v[i],dp[i - 1][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[i - 1][j - w[i] * k] + c[i] * k,dp[i - 1][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[i - 1][j],dp[i - 1][j - w[i]] + c[i],dp[i - 1][j - w[i] * 2] - c[i] * 2,...)\)
然鹅

\(dp[i][j - c[i]] = max(dp[i - 1][j - w[i]] + c[i],dp[i - 1][j - w[i] * 2] + c[i] * 2,dp[i - 1][j - w[i] * 3] + c[i] * 3,...)\)
所以

\(dp[i][j] = max(dp[i - 1][j],dp[i][j - 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][j - c[i]] + w[i], dp[i - 1][j]);
        } else {
            dp[i][j] = dp[i - 1][j];
        }
    }
}

简单DP问题

Frog 1

思路

这个题明显是一个构造题,首先构造 \(dp_i\) 表示跳到 \(i\) 为止,最小的花费,那么对于从 \(1~n\) 的枚举过程中,显然 \(dp_i = min(dp_{i-1}+abs(h_i - h_{i-1}),dp_{i-2}+abs(h_i-h_{i-2}))\)。知道这个,问题就迎刃而解了。

代码
点击查看代码
#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$ 次,于是我们可以枚举,对于 $dp_i$ 来说,$dp_i = min(dp_i,dp_j + abs(h_i - h_j))$,不断更新 $dp_i$ 的最小值,这个题就没了。
点击查看代码
#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 @ 2024-08-14 23:06  To_Carpe_Diem  阅读(16)  评论(0编辑  收藏  举报