蓝桥杯练习-3.2
视频学习
2019年蓝桥杯训练营(C++) 12.4常见动态规划模型练习题
• 最大子矩阵和
最大子矩阵和问题。给定m行n列的整数矩阵A,求矩阵A的一个子矩阵,使其元素之和最大。
思路:动态规划,是二维的最大子段和,将二维数组降维成一维数组然后按最大子段和的做法写,也就是将几行二维数组压缩成一维数组来写。
代码实现:
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
long long num[401][401];
long long presum[401][401];//压缩的数据,presum[i][j]表示第j列的前i项压缩的总和
int main()
{
int N, M;
long long sum, ans;
cin >> N >> M;
ans = -1000000001;
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <=M ; j++)
{
cin >> num[i][j];
ans = max(ans, num[i][j]);
}
}
if (ans <= 0)
{
cout << ans << endl;//防止矩阵中全是负数
}
else
{
for (int i = 1; i <= N; i++)
{
for (int j = 1; j <= M; j++)
{
presum[i][j] = presum[i-1][j] + num[i][j];//开始压缩二维数组了
}
}
for (int i = 1; i <= N; i++)//这个i循环就是控制搜索行,第一次可以从第一行一直压缩到最后一行,比较压的值
{ //第二次就只能从第二行开始往下扫了,这样可以保证所有的矩阵和情况都能包括
for (int j = i; j <= N; j++)//这个j循环便是控制压缩几行的,j+1,那么压缩的行也加1,presum[j][k]中的j就是压缩了前j行
{//这样在i循环里面,i的值不变,j++,那么减去的presum[i-1][k]一直是我们不要搜索的前面几行,也是i控制不搜的几行,所以要减去
sum = 0;//之所以每一次循环都要初始化为0,是因为往下增加压缩的行得到的sum,会包括前面的值,因为presum是压缩值,j+1的sum只是在j的基础上多加了一行,不然重复多加了
for (int k = 1; k <= M; k++)//k循环便是循环搜索每一行的每一列的值,横着来
{
if (sum + presum[j][k] - presum[i-1][k] < 0)//和最大子段和一样
{
sum = 0;
}
else
{
sum += presum[j][k] - presum[i-1][k];
}
ans = max(ans, sum);
}
}
}
}
cout << ans << endl;
return 0;
}
• 删除最少元素(最长下降子序列)
思路:
要先递减后递增,那么可以先正着做一次最长下降子序列 dp1[i],反着再做一次最长下降子序列 dp2[i],最后枚举看看dp1[i] + dp2[i] -1最大的是多少,然后再拿n减去即可,为什么-1,是因为dp1和dp2都包含了第i个数,重复加了
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int a[10001];
int dp1[10001],dp2[10001];//dp[i]表示以i结尾的最长下降子序列的长度
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
for (int i = 1; i <= n; ++i)
{
dp1[i] = 1;
for (int j = 1; j < i; ++j)
{
if (a[j] >= a[i])
{
dp1[i] = max(dp1[i], dp1[j] + 1);
}
}
}
for (int i = n; i >=1 ; i--)
{
dp2[i] = 1;
for (int j = n; j > i; j--)
{
if (a[j] >= a[i])
{
dp2[i] = max(dp2[i], dp2[j] + 1);
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
ans = max(dp1[i] + dp2[i] - 1, ans);
}
cout << n - ans << endl;
return 0;
}
• 闯关-LIS+最大
dp[i]代表前i关最大难度和,想办法从最前面的关卡一直尝试往这一关转移
代码实现:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int a[1001];
long long dp[1001];//dp[i]表示前i关最大难度和
int main()
{
int n;
long long ans = 0;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
for (int i = 0; i < n; i++)
{
dp[i] = a[i];//首先肯定把自己本身那关的难度累加进去
for (int j = 0; j < i; j++)
{
if (a[j] < a[i] && dp[j] + a[i] > dp[i])//如果前j关的最大难度和加上这关难度大于走过前i关的最大难度和(暂定)
{ //就更新,也就是状态转移
dp[i] = dp[j] + a[i];
}
ans = max(dp[i], ans);
}
}
cout << ans << endl;
return 0;
}
• 回文串-最长公共子序列
一个字符串如果从左往右读和从右往左读都一样,那么这个字符串是一个回文串。
例如:“abcba”,“abccba”。蒜头君想通过添加字符把一个非回文字符串变成回文串。例如:“trit”,可以添加一个’i’变成回文串"tirit"。请你用程序计算出,对于一个给定的字符串,最少需要添加几个字符,才能变成回文串。
输入格式:输入一个长度为 n (1,3000)的字符串。
输出格式:输出最少需要添加的字符个数,占一行。
样例输入:
trit
样例输出:
1
思路:
本质上就是求最长公共子序列,把所给字符串s1颠倒过来变成s2,然后求s1和s2的最长公共子序列长度,最后用字符串的长度减去最长公共子序列的长度即为最少需要添加的字符个数
代码实现:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
string s1, s2;
int dp[3005][3005];
int main()
{
cin >> s1;
s2 = s1;
reverse(s2.begin(), s2.end());//将字符串s1颠倒,给s2,注意reverse函数的使用
for (int i = 1; i <= s1.size(); ++i)
{
for (int j = 1; j <=s2.size(); ++j)
{
if (s1[i-1] == s2[j-1])
{
dp[i][j] = dp[i-1][j-1] + 1;
}
else
{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
cout << s1.size() - dp[s1.size()][s2.size()] << endl;
return 0;
}