今天做了学长拉的LIS和LCS以及背包问题的专题,因此就在这做个总结。
首先讲一下关于背包问题,
背包中最关键的无非就是01背包,以及完全背包,而且这两个代码也都很好写,也很好理解(就循环遍历的顺序变了),除此之外还有多重背包,二维费用的背包,以及混合背包,但万变不离其宗,都是由01背包演变而来,而且大都可以变成01背包的形式。
关于背包问题的讲解,大家可以去网上搜一下dd大佬的背包九讲,这可是经典! 当然这也有链接:背包九讲
下面贴一些01背包以及多重背包的题型。
hdu2602——01背包模板 | 题解 |
hdu2191——多重背包模板 | 题解 |
hdu2546——01背包理解 | 题解 |
Robberies ——01背包变形(思维题) | 题解 |
hdu2639——01背包+第k优解问题 | 题解 |
hdu1171-——多重背包理解 | 题解 |
都是一些水题,不过可以加深一下自己看完背包九讲后的印象。
讲完背包问题就是LCS和LIS(两种实现)
先将LCS吧
LCS(longest common sequence) ,最长公共子序列,是一个比较经典的dp基础问题,也是很重要的,很多比赛都有从它变形出来的题,其中主要的题型就是记录和输出路径。
先上裸的代码
//递归写法,时间复杂度很不友好!!慎用。
//而且我们有爸爸妈妈都喜欢的递推写法,所以。。。
#include <iostream>
#include <cstring>
using namespace std;
char str1[205],str2[205];
int len1,len2;
int maxlen(int i, int j)
{
if(j == 0 || i == 0)//如果有一个为0,代表就没有相同的
return 0;
if(str1[i-1] == str2[j-1])//如果相同的话
return maxlen(i-1,j-1)+1;
else//不同的话,这个也很好想到。
return max(maxlen(i-1,j),maxlen(i,j-1));
}
int main(int argc, char** argv) {
while(cin >> str1 >> str2)
{
len1 = strlen(str1);
len2 = strlen(str2);
cout<<maxlen(len1,len2)<<endl;
}
return 0;
}
//递推写法,比较经典.
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
char str1[20005],str2[20005];
int len1,len2;
int maxlen[2005][2005];
int main(int argc, char** argv) {
while(cin >> str1 >> str2)
{
len1 = strlen(str1);
len2 = strlen(str2);
for(int i = 0;i <= len1; i++)
maxlen[i][0] = 0;
for(int j = 0;j <= len2; j++)
maxlen[0][j] = 0;
for(int i = 1 ;i <= len1; i++){
for(int j = 1; j <= len2; j++){
if(str1[i-1]==str2[j-1]) maxlen[i][j] = maxlen[i-1][j-1] + 1;
else maxlen[i][j] = max(maxlen[i-1][j],maxlen[i][j-1]);
}
}
cout << maxlen[len1][len2]<<endl;
}
return 0;
}
其实这个是比较好理解的,状态转移方程式
if(str1[i]==str2[j])
dp[i][j]=dp[i-1][j-1]+1
else
dp[i][j]=max(dp[i][j-1],dp[i-1][j])
dp[i][j]表示在str1的前i个字母和str2的前j个字母里面有多少个相同的。
然后就是LIS,最长上升子序列(类似有下降,严格上升之类的)
首先抛出O(n*n)的写法
#include <iostream>
using namespace std;
const int maxn = 100000 + 10;
int a[maxn];
int dp[maxn];
int main(int argc, char** argv) {
int n;
cin >> n;
for(int i = 0; i < n; i++){
cin >> a[i];
dp[i]=1;
}
for(int i = 1; i < n; i++)
for(int j = 0; j < i; j++)
if(a[j] < a[i] )
dp[i]=max(dp[j]+1,dp[i]);
int maxlen=0;
for(int i = 0; i < n; i++)
if(maxlen<dp[i])
maxlen=dp[i];
cout << maxlen <<endl;
return 0;
}
再来一个O(n*logn)的写法,其实就是二分一下
#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 50001;
int binary_search(int key, int *g, int low, int high)
{
while (low < high)
{
int mid = (low + high) >> 1;
if (key >= g[mid])
low = mid + 1;
else
high = mid;
}
return low;
}
int main()
{
int i, j, a[maxn], g[maxn], n, len;
while (cin >> n)
{
memset(g, 0, sizeof(g));
for (i = 0; i < n; i++)
cin >> a[i];
g[1] = a[0], len = 1;//初始化子序列长度为1,最小右边界
for (i = 1; i < n; i++)
{
if (g[len] < a[i])//(如果允许子序列相邻元素相同 g[len]<=a[i],默认为不等)
j = ++len; //a[i]>g[len],直接加入到g的末尾,且len++
else
j = binary_search(a[i], g, 1, len + 1); //可以用STL中的lower_bound;
g[j] = a[i];//二分查找,找到第一个比a[i]小的数g[k],并g[k+1]=a[i]
}
cout << len << endl;
}
return 0;
}
也可以借用STL中的lower_bound,
int n;
cin>>n;
int dp[1005],a[1005];
for(int i = 1;i<=n;++i){
scanf("%d",&a[i]);
dp[i]=1;
}
int k = 1;
dp[k]=a[1];
for(int i = 2;i<=n;++i){
if(dp[k]<a[i]) dp[++k]=a[i];
else *lower_bound(dp+1,dp+1+k,a[i])=a[i];
}
printf("%d\n",k);
状态转移方程的话,dp[i]=max(dp[j]+1,dp[i])(a[j]<a[i]&&j<i);
dp[i]表示在前i个数里面有多少个上升的数。
最长公共子序列(LCS)模板题 | 上面有。 |
poj2533——O(nlogn)LIS算法 | 上面有。 |
poj1836——正逆两次LIS求最大值 | 题解 |
hdu1160——二维LIS+输出路径 | 题解 |
poj2250——LCS记录路径 | 题解 |
hdu1503——LCS路径输出 | 题解 |