动态规划专题

入门
以前写过的几个题
1
2

力扣简单题 刷题记录
1.最大连续子数组的和
2.买卖股票 ________维护一下第i天之前的最小值,然后遍历一遍维护Max即可
3.最小花费爬楼梯
4.不连续的子序列最大和(任意两个元素都不能相邻)
5.最少操作数复制出 n 个 A
6.坐飞机,算是个思维+数学吧, 求概率
7.水题,求数组中等差数列的个数
8.打家劫舍 同第四个
9.打家劫舍 2 ___ 把环分成两个单排
10.整数拆分 ___ 记忆化搜索
11.完全背包问题 用最少的硬币数构成amount,准确构成 ____因为是要求恰好价值为amount,所以我们要给 dp 数组初始化,使其从 0 开始计数;又因为要求最小值,所以初始化为INF,求最大值则置为 -INF
12.用几种硬币恰好组成 n元,求方案数 即背包问题求方案数 __ 背包九讲 __这个题的话首先要明确:背包容量其实就是金额 n,然后每个物品的价值和体积其实是一样的,ps:值得琢磨一下这道题,也可以用数学解
13.不同路径 递推水题
14.不同路径 2 在1的基础上一些判断条件即可
15.三角形最小和 递推___注意 f 数组初始化正无穷
16.完全平方数 简单完全背包

进阶
力扣
算法题,其实不是先有了公式或方法才有思路,而是先有思路、把思路用公式、算法表示出来才有了公式、算法。dp的递推公式不是本质,本质是理解动态规划是带记事本的暴力循环,一定要先求子问题,注意for循环的方向
1.最长回文子序列 注意两层for循环遍历方向(很重要, 开始遍历的是 i 和 j 差值绝对值小的,表示先计算子问题, 如果开始遍历的是i
和 j 差值大的,那么我们需要多次遍历,遍历320次就会 T),自身起始要赋为1,定义 f[j][i]代表的是从 j 到 i 范围内的最长回文子序列
2.最长公共子序列
3.最长回文子串(子串是连续的)
枚举端点,第一层循环枚举长度,第二层枚举左端点,计算一下就是右端点,这样就可以枚举所有端点(妙哉)
还可以由中心扩展,中间部分相同,然后两边对称扩展
4.编码方法___易错,仔细推
5.比特位计数___直接算思路简单, 动态规划解法可以打表找规律发现递推公式
6.乘积最大子数组 (连续的)
7.单词拆分
8.最大正方形
9.回文串的个数 最长回文子串改一下就能过
10.分割等和子集
11.连续的子数组和 _______和最前边 “1” 里的幸运数组差不多,不过幸运数组的数据好像没有力扣的强?

砝码称重
先看图
在这里插入图片描述

#include <bits/stdc++.h>

using namespace std;

const int N = 110, M = 2e5 + 10;
int sum;
int n;
int w[N];
bool f[N][M];

int main() {

    cin>>n;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &w[i]);
        sum+=w[i];
    }

    f[0][0]=true;// 边界

    for (int i = 1; i <= n;i++)
        for (int j = 0; j <=sum;j++)
            f[i][j] = f[i-1][j] || f[i-1][j+w[i]] || f[i-1][abs(j-w[i])];
                //只要有一个非空,f[i][j]就非空
    int ans = 0;
    for (int i = 1; i <= sum;i++)// 求正整数,从1开始枚举
        if(f[n][i])ans++;//不为零说明可以选出这个质量的砝码

    cout << ans;

    return 0;
}

过桥
在这里插入图片描述
第二种情况下,1和2先过桥,然后利用1运人,2接1。然后重复

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[1005],f[1005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	
	f[1]=a[1]; f[2]=a[2];// 初始化
	
	for(int i=3;i<=n;i++)
	 f[i]=min(f[i-1]+a[1]+a[i], f[i-2]+a[1]+a[i]+a[2]+a[2]);
	//第一个表示:让1回来接人
	//第二个表示:让1回来把手电筒给i,然后i和i-1一起过去,2再回来接1
	printf("%d",f[n]);
	return 0;
}

hdu 1025
前置知识:Dilworth定理

最少的下降序列个数就等于整个序列最长上升子序列(严格递增)的长度(配 lower_bound
不下降子序列(配 upper_bound

最长上升子序列 O(nlogn), 数据太大n²会T。
所以我们将最长上升子序列放入到一个数组里面,每轮到一个元素,如果它比当前最长上升子序列的最后一个元素大,就将其放入末尾;否则就在当前最长子序列里用二分查找,找到第一个大于等于它的位置,将其放入。

还有就是这个nlogn的方法只能求出长度,无法得到真正的序列。

二分可以用 lower_bound
最后输出是个wa点
code:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 9;
int n, t;
int a, b;
int x[N];
int f[N];
int _;
int binary(int l, int r, int num)// 二分求第一个大于等于num的数
{
	while(l <= r)
	{
		int mid = l - (l - r) / 2;
		if(f[mid] == num) return mid;
		else if(f[mid] > num)
			r = mid - 1;
		else l = mid + 1;
	}
	return l;
}
void work()
{
	for(int i = 1; i <= n; ++i)
	{
		scanf("%d %d", &a, &b);
		x[a] = b;
	}
	int j = 1;
	f[1] = x[1];
	for(int i = 2; i <= n; ++i)
	{
		if(x[i] > f[j])
			f[++j] = x[i];
		else 
		{
			int pos = lower_bound(f + 1, f + j + 1, x[i]) - f;
			pos = binary(1, j, x[i]);
			// 两个二分方法
			f[pos] = x[i];
		}
	}
	printf("Case %d:\n", ++_);
	if(j == 1)
	printf("My king, at most %d road can be built.\n\n", j);
	else 
	printf("My king, at most %d roads can be built.\n\n", j);
}
int main()
{
	while(~scanf("%d", &n))
		work();
	return 0;
}

C1. Pokémon Army (easy version)
wa了一个小时,没改出来
网上一搜这个代码居然a了,我直接震惊,擦
这可能就是贪心解法?

#include<bits/stdc++.h>
#define ll long long
#define ios ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);
using namespace std;
const int maxn=3e5+5,INF=0x3f3f3f3f;
ll a[maxn];
int main()
{
    ios;
    int t;
    cin >> t;
    while(t--)
    {
        int n, q;
        cin >> n >> q;
        for(int i = 1; i <= n; i++)
            cin >> a[i];
        a[n + 1] = 0;
        ll ans = 0;
        for(int i = 1; i <= n; ++i)
        {
        	if(a[i + 1] < a[i])
        		ans += a[i] - a[i + 1];
		}
        cout << ans << endl;
    }
    return 0;
}

dp解法
粘一下博客叭
到每个数字无非三个选择, - 或者 + 或者不选
每个存的都是 + 或 不选 ,- 或 不选

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 9;
ll dp[N][2];
ll a[N];
int main()
{
	int t, n, q;
	cin >> t;
	while(t--)
	{
		memset(dp,0,sizeof(dp));
		cin >> n >> q;
		a[n+1] = 0;
		for(int i = 1; i <= n; ++i)	scanf("%lld", &a[i]);
		for(int i = 1; i <= n; ++i)
		{
			dp[i][0] = max(dp[i-1][0], dp[i-1][1] - a[i]);
			dp[i][1] = max(dp[i-1][1], dp[i-1][0] + a[i]);
		}
		cout << max(dp[n][0], dp[n][1]) << endl;
	}
	return 0;
}

把n分解成若干个不小于k的数的和的方案
当 i < 2 * k时,只有一种情况
当 i >= 2 * k时
在这里插入图片描述
在这里插入图片描述
f 数组存的就是 n = i 时的方案数

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e6 + 9;
int t, n, m, k;
int f[N];
int main()
{
	cin >> n >> k;
	f[k] = 1;
	long long ans = 1;
	for(int i = k ; i <= n - k; ++i)
	{
		f[i] = 1;
		if(i < 2 * k)  continue;
		f[i] = (f[i - 1] + f[i - k]) % mod;
	}
	for(int i = k; i <= n - k; ++i)
	{
		ans += f[i];
		ans %= mod;
	}
	cout << ans << endl;
	return 0;
}

山东省2021省赛 H题
二位费用背包
加了一个可转换的条件

#include<bits/stdc++.h>
using namespace std;
int n, H, S, h, s, w;
long long f[1009][1009];
long long ans = 0;
int main()
{
	cin >> n >> H >> S;
	for(int i = 1; i <= n; ++i)
	{
		scanf("%d %d %d", &h, &s, &w);
		for(int j = H; j > 0; --j)
		{
			for(int k = S; k >= 0; --k)
			{
				int h1 = j - h, s1 = k - s;
				if(s1 < 0)// 如果耐力不够就消耗生命值
					h1 += s1, s1 = 0;
				if(h1 > 0)
					f[j][k] = max(f[j][k], f[h1][s1] + w);
			}
		}
	}
	printf("%lld\n", f[H][S]);
	return 0;
}

最优编辑 最优包含题解
最优编辑题目链接
分析:
设dp[ i ][ j ]为将字符串A[ 1,…,i ] 转变为字符串 B[ 1,…,j ]所用的最小字符操作次数。
    边界情况:
    dp[ 0 ][ 0 ] = 0 (两个空串)
    dp[ i ][ 0 ] = i (对A[ 1,…,i ] 重复进行 i 次删除操作)
    dp[ 0 ][ j ] = j (对空串A重复进行 j 次插入操作)
    递推式:
    dp[ i ][ j ] = dp[ i ][ j-1 ] + 1 ( 在A[ 1,…,i ]的尾部插入B[ j ] )
    dp[ i ][ j ] = dp[ i-1 ][ j ] + 1 ( 删去A[ 1,…,i ]的尾部A[ i ] )
    dp[ i ][ j ] = dp[ i-1 ][ j-1 ] + (A[ i ]==B[ j ] ? 0 : 1 ) (如果不相等则将A[ i ]替换为B[ j ],否则不需要替换 )
    dp[ i ][ j ] 在上述三种可能中取最小(对应于三种操作)
最优包含题目链接

#include<bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
string s, t;
int f[1009][1009];
int main()
{
	cin >> s >> t;
	int ls = s.length(), lt = t.length();
	//memset(f,0x3f,sizeof(f));  全置为inf比较慢,但是好理解,存的一定从最开始的开始状态开始转移
	for(int i = 1; i <= lt; ++i) f[0][i] = inf;
	// 这个赋值稍微难理解
	for(int i = 0; i <= ls; ++i) f[i][0] = 0;
	for(int i = 1; i <= ls; ++i)
	{
		for(int j = 1; j <= lt; ++j)
		{
			int d = s[i-1] == t[j-1] ? 0 : 1;
			f[i][j] = min(f[i-1][j], f[i-1][j-1] + d);
		}
	}
	cout << f[ls][lt] << endl;
	return 0;
}

hdu-1024-最大和连续子序列增强版
简单版
思路:
初始考虑
状态定义: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示把前 j j j 个数组分成 i i i 组的区间和。
转移方程: d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , d p [ i − 1 ] [ j − 1 ] ) + a [ j ] dp[i][j] = max(dp[i][j - 1], dp[i - 1][j - 1]) + a[j] dp[i][j]=max(dp[i][j1],dp[i1][j1])+a[j]
因为二维dp会炸,需要优化
code:

#include<bits/stdc++.h>
using namespace std;
 
const int maxn = 1e6;
const int inf = 0x3f3f3f3f;
 
long long dp[maxn + 5];
long long a[maxn +5];
long long pre[maxn + 5];
int n, m;
 
int main()
{
	while(~scanf("%d %d", &m, &n))
	{	
		memset(dp, 0, sizeof(dp));
		memset(pre, 0, sizeof(pre));
		for(int i = 1; i <= n; i++)
			scanf(" %lld", &a[i]);
		long long tmp;
		for(int i = 1; i <= m; i++)
		{
			tmp = -inf;
			for(int j = i; j <= n; j++)
			{
				dp[j] = max(dp[j - 1], pre[j - 1]) + a[j];
				pre[j - 1] = tmp;
				tmp = max(tmp, dp[j]);
			}
		}
		printf("%lld\n", tmp);
	}
	return 0;
}

hdu-1069-Monkey and Banana
堆叠长方体,要求是上层的长方体的长宽是严格小于下层的min(长,宽),对于每种长方体可以任意底面摆放。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 200;
int n, cnt = 1;
struct Block
{
	int x, y, high;
	int dp;
	bool operator <(const Block &q)const{
		if(x != q.x) return x < q.x;
		else if(y != q.y) return y < q.y;
		else return 0;
	}
}block[maxn];
int main()
{
	while(scanf("%d",&n), n)
	{
		int a, b, c, k = 0;
		for(int i = 1; i <= n; ++i)
		{
			scanf("%d %d %d", &a, &b, &c);
			if(a==b&&b==c)
            {
                block[k].x=a,block[k].y=b,block[k].high=c;block[k].dp=block[k].high;k++;
            }
            else if(a==b&&a!=c)
            {
                block[k].x=a,block[k].y=b,block[k].high=c;block[k].dp=block[k].high;k++;
                block[k].x=a,block[k].y=c,block[k].high=b;block[k].dp=block[k].high;k++;
                block[k].x=c,block[k].y=a,block[k].high=b;block[k].dp=block[k].high;k++;
            }
            else if(a==c&&a!=b)
            {
                block[k].x=a,block[k].y=c,block[k].high=b;block[k].dp=block[k].high;k++;
                block[k].x=a,block[k].y=b,block[k].high=c;block[k].dp=block[k].high;k++;
                block[k].x=b,block[k].y=a,block[k].high=c;block[k].dp=block[k].high;k++;
            }
            else if(b==c&&b!=a)
            {
                block[k].x=b,block[k].y=c,block[k].high=a;block[k].dp=block[k].high;k++;
                block[k].x=b,block[k].y=a,block[k].high=c;block[k].dp=block[k].high;k++;
                block[k].x=a,block[k].y=b,block[k].high=c;block[k].dp=block[k].high;k++;
            }
            else
            {
                block[k].x=a,block[k].y=b,block[k].high=c;block[k].dp=block[k].high;k++;
                block[k].x=a,block[k].y=c,block[k].high=b;block[k].dp=block[k].high;k++;
                block[k].x=b,block[k].y=a,block[k].high=c;block[k].dp=block[k].high;k++;
                block[k].x=b,block[k].y=c,block[k].high=a;block[k].dp=block[k].high;k++;
                block[k].x=c,block[k].y=a,block[k].high=b;block[k].dp=block[k].high;k++;
                block[k].x=c,block[k].y=b,block[k].high=a;block[k].dp=block[k].high;k++;
            }
		}
		sort(block, block + k);
		int ans = -1;
		//每个block[i]为当前最底层 
		for(int i = 0; i < k; i++)
        {
            for(int j = 0; j < i; j++)
            {
                if(block[i].x > block[j].x  && block[i].y > block[j].y)
                block[i].dp = max(block[j].dp + block[i].high, block[i].dp);
            }
            ans = max(ans, block[i].dp);
        }
        printf("Case %d: maximum height = %d\n",cnt++,ans);
	}
	return 0;
}

F 项链
是道线性dp,但是看起来不简单的样子

D - National Railway
矩阵上的dp

ZOJ-3662-Math Magic
困难dp

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值