LIS
最长递增子序列,朴素的是o(n^2)算法,二分下可以写成o(nlgn):维护一个当前最优的递增序列——找到恰好大于它更新
LCS
最长公共子序列,通常o(n^2)的算法
********************************************************************************************************************
LIS:
题目大意:给定序列个数n及n个数,求该序列的最大连续子序列的和,要求输出最大连续子序列的和以及子序列的首位位置
解题思路:经典DP,可以定义
dp[i]表示以a[i]为结尾的子序列的和的最大值,因而最大连续子序列及为dp数组中的最大值。
状态转移方程:dp[1] = a[1]; //以a[1]为结尾的子序列只有a[1];
i >= 2时, dp[i] = max( dp[i-1]+a[i], a[i] );
/* HDU 1003 Max Sum --- 经典DP */
#include <cstdio>#include <cstring>
int dp[100005];
int main()
{
int t, n;
int kase = 0;
int fst, lst, maxSum; //记录首位位置以及最大和
int start; //start是用于记录中间变化的起点的
scanf("%d", &t);
while (t--)
{
scanf("%d", &n);
for (int i = 0; i < n; ++i)
{
scanf("%d", dp + i);
}
start = fst = lst = 0;
maxSum = dp[0];
for (int i = 1; i < n; ++i)
{ //dp[i] = MAX(dp[i - 1] + dp[i], dp[i]);
if (dp[i - 1] >= 0)
{
dp[i] = dp[i - 1] + dp[i];
}
else
{
start = i; //抛弃dp[i-1],则起点发生变化 }
if (dp[i] > maxSum)
{ //若当前求得的子序列和最大,进行更新
maxSum = dp[i];
fst = start;
lst = i;
}
}
if (kase)
{
printf("\n");
}
printf("Case %d:\n", ++kase);
printf("%d %d %d\n", maxSum, fst + 1, lst + 1);
}
}
return 0;
}
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int a[1010], dp[1010];
#define inf 0x3f3f3f3f
int main()
{
int n;
while (cin >> n&&n)
{
memset(a, 0, sizeof(a));
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++) cin >> a[i];
int ans, maxn = 0;
for (int i = 1; i <= n; i++)
{
ans = -inf;
for (int j = 0; j < i; j++)
{
if (a[i] > a[j]) ans = max(ans, dp[j]);
}
dp[i] = ans + a[i];
maxn = max(maxn, dp[i]);
}
cout << maxn << endl;
}
return 0;
}
*************************************************************************************************************
LCS:
abcfbc abfcab。
辅助空间变化示意图
可以看出:
F[i][j]=F[i-1][j-1]+1;(a[i]==b[j])
F[i][j]=max(F[i-1][j],F[i][j-1])(a[i]!=b[j]);
n由于F(i,j)只和F(i-1,j-1), F(i-1,j)和F(i,j-1)有关, 而在计算F(i,j)时, 只要选择一个合适的顺序, 就可以保证这三项都已经计算出来了, 这样就可以计算出F(i,j). 这样一直推到f(len(a),len(b))就得到所要求的解了.
#include<stdio.h>
#include<string.h>
int f[1001][1001];
int main()
{
char a[1001], b[1001];
int i, j, len1, len2;
while (~scanf("%s %s", a, b))
{
len1 = strlen(a);
len2 = strlen(b);
for (i = 0; i <= len1; i++)
{
f[i][0] = 0;
}
for (i = 0; i <= len2; i++)
{
f[0][i] = 0;
}
for (i = 1; i <= len1; i++)
{
for (j = 1; j <= len2; j++)
{
if (a[i - 1] == b[j - 1])
{
f[i][j] = f[i - 1][j - 1] + 1;
}
else
{
f[i][j] = f[i - 1][j]>f[i][j - 1] ? f[i - 1][j] : f[i][j - 1];
}
}
}
printf("%d\n", f[len1][len2]);
}
return 0;
}
解题思路:这道题就是给你两个单词,然后你要把两个单词拼接成一个新单词,使得新单词的子序列中包含两个单词,并且要使这个新单词最短。所以这道题就是求最长公共子序列,并且要记录下子序列的字母,以及他们在主串和副串中的原始位置,之后进行拼接输出。
Sample Input
apple peach ananas banana pear peach
Sample Output
appleach bananas pearch
递归输出:
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f
char s1[110], s2[110];
int dp[110][110], mark[110][110];
int len1, len2;
void LCS()
{
int i, j;
memset(dp, 0, sizeof(dp));
for (i = 0; i <= len1; i++) mark[i][0] = 1;
for (i = 0; i <= len2; i++) mark[0][i] = -1;
for (i = 1; i <= len1; i++)
{
for (j = 1; j <= len2; j++)
{
if (s1[i - 1] == s2[j - 1])
{
dp[i][j] = dp[i - 1][j - 1] + 1;
mark[i][j] = 0;
}
else if (dp[i - 1][j] >= dp[i][j - 1])
{
dp[i][j] = dp[i - 1][j];
mark[i][j] = 1;
}
else
{
dp[i][j] = dp[i][j - 1];
mark[i][j] = -1;
}
}
}
}
void PrintLCS(int i, int j)
{
if (!i && !j) return;
if (mark[i][j] == 0)
{
PrintLCS(i - 1, j - 1);
//cout << s1[i - 1];
printf("%c", s1[i - 1]);
}
else if (mark[i][j] == 1)
{
PrintLCS(i - 1, j);
// cout << s1[i - 1];
printf("%c", s1[i - 1]);
}
else
{
PrintLCS(i, j - 1);
// cout << s2[j - 1];
printf("%c", s2[j - 1]);
}
}
int main()
{
while (~scanf("%s%s", s1, s2))
{
len1 = strlen(s1);
len2 = strlen(s2);
LCS();
PrintLCS(len1,len2);
cout << endl;
}
return 0;
}
推荐:
uva 10635 Prince and Princess LCS转化成LIS
hdu 4352 XHXJ's LIS 数位dp+LIS思想
srm div2 1000 状态压缩+LIS
poj 1239 Increasing Sequence 两次dp
uva 111 History Grading 要先排个序
End