力扣简单题 刷题记录
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][j−1],dp[i−1][j−1])+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