Eva is trying to make her own color stripe out of a given one. She would like to keep only her favorite colors in her favorite order by cutting off those unwanted pieces and sewing the remaining parts together to form her favorite color stripe.
It is said that a normal human eye can distinguish about less than 200 different colors, so Eva's favorite colors are limited. However the original stripe could be very long, and Eva would like to have the remaining favorite stripe with the maximum length. So she needs your help to find her the best result.
Note that the solution might not be unique, but you only have to tell her the maximum length. For example, given a stripe of colors {2 2 4 1 5 5 6 3 1 1 5 6}. If Eva's favorite colors are given in her favorite order as {2 3 1 5 6}, then she has 4 possible best solutions {2 2 1 1 1 5 6}, {2 2 1 5 5 5 6}, {2 2 1 5 5 6 6}, and {2 2 3 1 1 5 6}.
Input Specification:
Each input file contains one test case. For each case, the first line contains a positive integer N (≤200) which is the total number of colors involved (and hence the colors are numbered from 1 to N). Then the next line starts with a positive integer M (≤200) followed by M Eva's favorite color numbers given in her favorite order. Finally the third line starts with a positive integer L (≤104) which is the length of the given stripe, followed by L colors on the stripe. All the numbers in a line a separated by a space.
Output Specification:
For each test case, simply print in a line the maximum length of Eva's favorite stripe.
Sample Input:
6
5 2 3 1 5 6
12 2 2 4 1 5 5 6 3 1 1 5 6
Sample Output:
7
翻译
伊娃试图用给定的颜色条纹制作自己的颜色条纹。她想通过剪掉那些不需要的部分并将剩余部分缝合在一起形成她最喜欢的颜色条纹,只保留她最喜欢的颜色。据说正常人眼能分辨的颜色不到200种,所以伊娃最喜欢的颜色是有限的。然而,原来的条纹可能很长,而 Eva 想要拥有最大长度的剩余最喜欢的条纹。所以她需要你的帮助才能找到最好的结果。请注意,解决方案可能不是唯一的,但您只需告诉她最大长度即可。例如,给定一条颜色条纹 {2 2 4 1 5 5 6 3 1 1 5 6}。如果 Eva 最喜欢的颜色按照她最喜欢的顺序给出 {2 3 1 5 6},那么她有 4 个可能的最佳解决方案 {2 2 1 1 1 5 6},{2 2 1 5 5 5 6},{2 2 1 5 5 6 6},和 {2 2 3 1 1 5 6}。
输入规范:每个输入文件包含一个测试用例。对于每种情况,第一行包含一个正整数 N(≤200),这是涉及的颜色总数(因此颜色从 1 到 N 编号)。然后下一行以正整数 M(≤200)开始,后面是 M Eva 最喜欢的颜色编号,按照她最喜欢的顺序给出。最后第三行以一个正整数 L (≤10 4 ) 开始,它是给定条纹的长度,后面是条纹上的 L 种颜色。一行中的所有数字均以空格分隔。
输出规范:对于每个测试用例,只需在一行中打印 Eva 最喜欢的条纹的最大长度。
常规解法,也是相对来说好理解的:
1.求最长不下降子序列的长度
AC
#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int INF = 1e5;
int main()
{
int n, m, l;
int arr[210];
int line[10010];
int dp[10010];
cin >> n >> m;
fill(arr, arr + n+10, INF);
//填充一定要填满,不然可能会有意想不到的错误
//不要直接填到n,多填一些防止出错!
//直接填到n不能AC
int index;
for (int i = 0; i < m; i++)
{
cin >> index;
arr[index] = i;
}
cin >> l;
for (int i = 0; i < l; i++)
{
cin >> line[i];
}
for (int i = 0; i < l; i++)
{
dp[i] = 1;
for (int j = 0; j < i; j++)
{
if (arr[line[i]] >= arr[line[j]] && dp[j] + 1 > dp[i] && arr[line[i]] != INF)
dp[i] = dp[j] + 1;
}
}
cout << *max_element(dp, dp + l) << endl;
return 0;
}
解析
解法二:
最长公共子序列更改模型
代码更加简洁明了,但不太好理解,我想了好几天。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <algorithm>
using namespace std;
//复杂问题,多重循环就要考虑动态转移/规划
int n, m, L;
int a[210], b[10010];//开两个数组存放数据
int dp[210][10010];//最终的dp[i][j]为所求长度
//开设的数组内存过大,不能放在main函数中,要放到全局中
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> a[i];
cin >> L;
for (int i = 1; i <= L; i++)
cin >> b[i];
for (int i = 0; i <= m; i++)//设置边界
dp[i][0] = 0;
for (int i = 0; i <= L; i++)
dp[0][i] = 0;
for(int i=1;i<=m;i++)
for (int j = 1; j <= L; j++)
{
if (a[i] == b[j])
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + 1;
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
cout << dp[m][L];
return 0;
}
下面是解析和思考:
沉思了好几天,终于想明白了
先说明比较特殊的解法。
改造公共子序列的模型
公共子序列的理解
参考:
动态规划 最长公共子序列 过程图解_Running07的博客-CSDN博客
最长公共子序列 - 动态规划 Longest Common Subsequence - Dynamic Programming_哔哩哔哩_bilibili
理解:
动态规划就是填表的过程,一般都是用于多重复杂循环有非常多没必要的重复循环步骤
用动态规划改进,优化时间复杂度
数组dp[i][j]
- 和[j]分别表示一个序列
首先是总体理解
我们从最后的情况开始思考比较好理解,假设现在是对最后一对元素的判断,判断他们是否为公共字序,
如果相等,那显然结果就是dp[i-1][j-1]+1--从最后的两个元素开始划分公共子序列,相等就把两个元素圈起来作为一个公共字序,+1就表示公共字序+1,既然判断完了那么就可以删去这两个元素了,所以dp[i-1][j-1]+1.
如果不相等,首先我们是从尾端开始分化子序列,不相等那么这两个子序不公共就+0,而我们要求最长公共子序,那我们就该进一步讨论,那么就该舍弃一个元素来凑对,跟下一个元素比对,看看能不能相等——就有两种情况,分别是[i]和[j]的序列舍弃,而我们进一步的探索他们是否会与当前元素为公共字序,此时我们不知道是否增长所以不用+1,要进到数组再次判断,而求最长就取这两种情况的最大值(因为不同情况可等可不等,相等的一定是最长的),所以是dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
如果两个结果相等,那也一样,最大值就代表他们俩,直接赋值就好了
之所以不相等时不考虑dp[i-1][j-1],是因为你这么做就等于直接舍去了dp[i - 1][j], dp[i][j - 1],这两种情况,直接去掉了舍去一个元素可能成为成为公共子序的可能,我们已经说明,思路是从最后开始分化的,我们从尾端分割序列,直接dp[i-1][j-1]是跳步,忽略前面的情况,不合理的!
至于为什么是-1而不是-2之类的。因为我们每一次循环只能遍历一次,一个元素,所以我们设定好每一次只判断一对元素。而这上一步就只能是移动一个位置。每次只能判一个元素所以相等只+1,只判断一对,而不是几对,每次循环只能移动一个下标才能实现所有情况的一对元素,所以是dp[i-1][j-1],dp[i - 1][j], dp[i][j - 1]!
(ps:之所以考虑舍弃就是因为我们可以确定现在这两个元素一定不相等,如果舍弃同样不相等那就不会影响结果。但是如果舍弃后重新比对相等了那公共子序列就会变长!!)。
进阶改造模型的理解。
思考
不相等->相等
相等->重复相等的最长
每一轮只遍历多一个元素,每一轮都只能移动一个元素
前面我们将相等时直接删去了两个元素,然后只加了1,这就显然是公共子序列中不能出现同样的元素重复才如此为之。因为一旦相同,那这两个元素就会被删去,然后仅仅只加了1次。
也就表示着两个序列的这两个元素仅仅只能生效一次,一旦相等就去除,表示已经作为了子序列,所以+1.
因此,为了实现重复元素的增加,也就是重复元素能够全部输出,就需要先将两个元素相等的情况输出,也就是先+1——然后用1步去进一步查找和判断重复元素的情况——目的就是为了让他们去接着匹配可能相等的最大结果,然后一旦相等就那公共子序列就会再次+1,然后循环就会再继续匹配,实现重复元素的叠加!
其实更改模型的本质就是如此,为了实现重复元素的输出,就在相等情况下保留了两个元素,让他们再与前面的所有元素去匹配,然后选出这两个重复元素最大的结果
从最后的完整序列开始配对,减轻思考负担。
不相等时:走一步也只能去匹配凑成相等的结果,也就是选择怎么截取为公共部分,操作只能进行一次所以不考虑重复;所以只有相等时才会思考重复相等的情况。
对于为何不能dp[i-2],[j-3]这么考虑多种情况,为什么不相等时只能变为相等不考虑重复元素?
一定要清楚:
- 这是一个填表的过程,环环相扣,把当前的一步想清楚的数据承接在下一步自然会得到多种情况和重复元素的结果
- 每一次循环只能遍历一个元素,我们设定也是定为一步。所以只是+1,下标只是挪动一位!所以你每一次判断重复元素也只能挪动一位,也就是只能考虑[i][j]分别下标-1的情况!
而你自己本身都不相等,你当然要通过移动一次下标来实现相等。而如此你已经移动了一次,你再判断重复再移动就相当于两次循环的情况了!都没有对齐循环和判断本身,显然是错误的。
总之我们每一次只要考虑当前的一次步骤,对应好循环次数和dp,剩下的交给循环和dp自己填充即可.
至于为何不相等时dp[i-1][j-1]不可用,前面说了这是跳步,不合理的!
题目本质上不是公共子序列,所以还是用最长不下降子列长度计算思考起来最好
因为这个解法的好处就是可以权重,重复元素的情况都是在模型上,可以直接用。
对于细节上和深入理解dp:
把自己想象成dp和元素的下标,想一下,
在dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])中
我们从dp[i][j]中进入到了dp[i - 1][j], dp[i][j - 1]进行访问
假设我们进入了dp[i - 1][j],那他的结果又是什么?
还是那些公式来求!
就变为
if (a[i] == b[j])
dp[i-1][j] = max(dp[(i - 1)-1][j], dp[i-1][j - 1]) + 1;
else
dp[i][j] = max(dp[(i - 1)-1][j], dp[i-1][j - 1]);
这种判断!
简而言之就是如果判断的两个元素相等,那当前情况是不是又相加一次?
那不就实现了重复元素的叠加?!
然后接着往下进入dp,不断进入下一个dp判断然后+1,这就做到了所有情况的讨论。
其他情况和结构也是这样的。把自己当成dp,想象成动态规划是一个递归递推的过程,把自己带入进去,进入到每一步的当前循环思考,就很好理解了。