1、最长递增子序列模板poj2533(时间复杂度O(n*n))
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[1005],a[1005];
int main()
{
int n;
while(scanf("%d",&n)>0)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=0;i<=n;i++)
dp[i]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
if(a[i]>a[j]&&dp[i]<dp[j]+1)
dp[i]=dp[j]+1;
}
int maxx=0;
for(int i=1;i<=n;i++)
if(dp[i]>maxx)
maxx=dp[i];
printf("%d\n",maxx);
}
return 0;
}
2、最长递增子序列模板poj3903(时间复杂度O(nlogn))
最长递增子序列,Longest Increasing Subsequence 下面我们简记为 LIS。
排序+LCS算法 以及 DP算法就忽略了,这两个太容易理解了。
假设存在一个序列d[1..9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。
下面一步一步试着找出它。
我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了
首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1
然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1
接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1..2] = 1, 5,Len=2
再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1..2] = 1, 3,Len = 2
继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1..3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。
第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len继续等于3
第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了
第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。
最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。
于是我们知道了LIS的长度为5。
!!!!! 注意。这个1,3,4,7,9不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个d[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到d[5], 9更新到d[6],得出LIS的长度为6。
然后应该发现一件事情了:在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说,我们可以使用二分查找,将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)~!
#include<iostream>
#include<stdio.h>
#include<stdio.h>
using namespace std;
int dp[100005],s[100005];
int main()
{
int n;
while(scanf("%d",&n)>0)
{
for(int i=0;i<n;i++)
scanf("%d",&s[i]);
dp[0]=s[0];
int len=1;
for(int i=1;i<n;i++)
{
int left=0,right=len-1,mid;
if(dp[len-1]<s[i])
dp[len++]=s[i];
else
{
right=len-1;
while(left<=right)
{
mid=(left+right)/2;
if(dp[mid]<s[i])
left=mid+1;
else right=mid-1;
}
dp[left]=s[i];
}
}
printf("%d\n",len);
}
return 0;
}
3、最长公共子序列poj1458
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[1000][1000];
char s[1000],t[1000];
int main()
{
while(scanf("%s%s",s,t)>0)
{
int lens=strlen(s),lent=strlen(t);
for(int i=0;i<1000;i++)
dp[0][i]=dp[i][0]=0;
for(int i=1;i<=lens;i++)
{
for(int j=1;j<=lent;j++)
if(s[i-1]==t[j-1])
dp[i][j]=dp[i-1][j-1]+1;
else
{
int maxx=0;
if(maxx<dp[i-1][j])
maxx=dp[i-1][j];
if(maxx<dp[i][j-1])
maxx=dp[i][j-1];
dp[i][j]=maxx;
}
}
printf("%d\n",dp[lens][lent]);
}
return 0;
}
4、最长公共递增子序列以及其路径记录问题
给两组数字个数分别为n,m的序列,问这两组序列的最长递增公共子序列是多长,并且输出来
数据:
Sample Input
5
1 4 2 5 -12
4
-12 1 2 4
Sample Output
2
1 4
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[1005][1005],a[1005],b[1005],path[1005][1005];
int f[1005];
int main()
{
int lena,lenb;
scanf("%d",&lena);
{
for(int i=0;i<lena;i++)
scanf("%d",&a[i]);
scanf("%d",&lenb);
for(int i=0;i<lenb;i++)
scanf("%d",&b[i]);
for(int i=0;i<=1004;i++)
dp[i][0]=dp[0][i]=0;
int tmp,ans=0;
int qd,zd,k=0,maxx;
for(int i=1;i<=lena;i++)
{
maxx=0;
for(int j=1;j<=lenb;j++)
{
dp[i][j]=dp[i-1][j];
if(a[i-1]>b[j-1]&&dp[i][j]>maxx)
{
maxx=dp[i][j];
k=j;
}
if(a[i-1]==b[j-1])
{
dp[i][j]=maxx+1;
path[i][j]=k;
}
//printf("%d\n",dp[i][j]);
if(ans<dp[i][j])
{
ans=dp[i][j];
qd=i;
zd=j;
}
}
}
printf("%d\n",ans);
int i=qd,j=zd;
int sum=ans;
if(ans>0)
f[ans--]=j-1;
while(ans&&i&&j)
{
if(path[i][j]>0)
{
f[ans--]=path[i][j]-1;
j=path[i][j];
}
i--;
}
for(int i=1;i<sum;i++)
printf("%d ",b[f[i]]);
printf("%d\n",b[f[sum]]);
}
return 0;
}
题目:
1、poj2250(单词当字母,记忆路径)
题意:给出两段单词,求这两段单词的最长公共单词数,并依次输出它们........
思路:总的来说,是赤裸裸的最长公共子序列,但是需要把单词当作字母来进行.......还需要记录下路径
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[1000][1000];
char path[200][200];
char s[200][50],t[200][50],sum[200][50];
int main()
{
while(scanf("%s",s[0])>0)
{
int lens=1;
while(1)
{
scanf("%s",s[lens++]);
if(s[lens-1][0]=='#')
break;
}
lens--;
int lent=0;
while(1)
{
scanf("%s",t[lent++]);
if(t[lent-1][0]=='#')
break;
}
lent--;
for(int i=0;i<1000;i++)
dp[i][0]=dp[0][i]=0;
memset(path,0,sizeof(path));
for(int i=1;i<=lens;i++)
{
for(int j=1;j<=lent;j++)
if(strcmp(s[i-1],t[j-1])==0)
{
dp[i][j]=dp[i-1][j-1]+1;
path[i][j]='1';
}
else if(dp[i-1][j]>dp[i][j-1])
{
dp[i][j]=dp[i-1][j];
path[i][j]='2';
}
else
{
dp[i][j]=dp[i][j-1];
path[i][j]='3';
}
}
int i=lens,j=lent,cnt=0;
//printf("111\n");
while(path[i][j]!=0)
{
int p=i,q=j;
if(path[p][q]=='1')
p--,q--;
else if(path[p][q]=='2')
p--;
else if(path[p][q]=='3')
q--;
if(dp[i][j]!=dp[p][q])
{
strcpy(sum[cnt++],s[i-1]);
//printf("%s\n",s[i]);
}
i=p;
j=q;
}
for(int f=cnt-1;f>0;f--)
printf("%s ",sum[f]);
printf("%s\n",sum[0]);
}
return 0;
}
2、poj1159(回文串的问题)
题意:给你一串字符,问加最少的字符可以使它编程回文串.......
思路:就是求出它正反的最长公共子串,然后用len减去这个长度,就是最少要加的字符数......
3、hdu4512(最长递增公共子序列问题)
有一天,有n个人按顺序站在他的面前,他们的身高分别是h[1], h[2] ... h[n],吉哥希望从中挑出一些人,让这些人形成一个新的队形,新的队形若满足以下三点要求,则称之为完美队形:
1、挑出的人保持他们在原队形的相对顺序不变;
2、左右对称,假设有m个人形成新的队形,则第1个人和第m个人身高相同,第2个人和第m-1个人身高相同,依此类推,当然,如果m是奇数,中间那个人可以任意;
3、从左到中间那个人,身高需保证递增,如果用H表示新队形的高度,则H[1] < H[2] < H[3] .... < H[mid]。
现在吉哥想知道:最多能选出多少人组成完美队形?
思路:也是把字符串倒过来正反取最长递增公共子序列,但是需要注意的是,i<j,还有,在i~~j的过程中,也许会有一个很大的数,那么也是可以加进去的,具体看代码
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[205][205],a[205];
int main()
{
int text;
scanf("%d",&text);
while(text--)
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
memset(dp,0,sizeof(dp));
int sum=0;
for(int i=1;i<=n;i++)
{
int maxx=0;
for(int j=n;j>i;j--)
{
dp[i][j]=dp[i-1][j];
if(a[i-1]>a[j-1]&&dp[i][j]>maxx)
maxx=dp[i][j];
if(a[i-1]==a[j-1])
dp[i][j]=maxx+1;
if(dp[i][j]*2>sum)
sum=dp[i][j]*2;
for(int k=i+1;k<j;k++)
{
if(a[k-1]>a[j-1]&&dp[i][j]*2+1>sum)
sum=dp[i][j]*2+1;
}
}
}
printf("%d\n",sum);
}
return 0;
}
4、hdu4512(必须距离k以上的最长递增子序列)
提起小明序列,他给出的定义是这样的:
①首先定义S为一个有序序列,S={ A1 , A2 , A3 , ... , An },n为元素个数 ;
②然后定义Sub为S中取出的一个子序列,Sub={ Ai1 , Ai2 , Ai3 , ... , Aim },m为元素个数 ;
③其中Sub满足 Ai1 < Ai2 < Ai3 < ... < Aij-1 < Aij < Aij+1 < ... < Aim ;
④同时Sub满足对于任意相连的两个Aij-1与Aij都有 ij - ij-1 > d (1 < j <= m, d为给定的整数);
⑤显然满足这样的Sub子序列会有许许多多,而在取出的这些子序列Sub中,元素个数最多的称为“小明序列”(即m最大的一个Sub子序列)。
例如:序列S={2,1,3,4} ,其中d=1;
可得“小明序列”的m=2。即Sub={2,3}或者{2,4}或者{1,4}都是“小明序列”。
当小明发明了“小明序列”那一刻,情绪非常激动,以至于头脑凌乱,于是他想请你来帮他算算在给定的S序列以及整数d的情况下,“小明序列”中的元素需要多少个呢?
思路:这是一个必须距离k以上的最长递增子序列,总的来说,就是开了一个记录路径的数组,说起来说不清楚,但是很神奇的样子,看代码就可以明白的......
#include<iostream>
#include<stdio.h>
#include<stdio.h>
using namespace std;
int dp[100005],s[100005],c[100005];
int main()
{
int n,k;
while(scanf("%d %d",&n,&k)>0)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&s[i]);
c[i]=10000000;
}
int len=0;
for(int i=1;i<=n;i++)
{
int left=1,right=n,mid;
while(left<=right)
{
mid=(left+right)/2;
if(s[i]>c[mid])
left=mid+1;
else right=mid-1;
}
dp[i]=left;
//printf("dp==%d\n",left);
if(dp[i]>len)
len=dp[i];
int j=i-k;
if(j>0&&c[dp[j]]>s[j])
c[dp[j]]=s[j];
//printf("c==%d\n",c[dp[j]]);
}
printf("%d\n",len);
}
return 0;
}
5、hdu4681(需要正反预处理的最长公共子序列)
题意:给你A,B,C三个字符串,其中A和B都包含C,现在要找这样一个D串,D串要包含C串,并且是A和B串的最长子串.......
超时思路:在A与B串中枚举C串的位置,然后截取掉,然后就是A串的开头与B串的开头的最长公共子序列,再是A串的结尾与B串的结尾的最长公共子序列,再加上C串的长度.....
ac思路:做预处理,先对A和B正序做一次最长公共子序列,再对A和B反序做一次最长公共子序列,然后分别在A串和B串中枚举C串出现的各个起始位置,记录下来,在进行比较,选择最长的......当然,要注意,反序的dp中,字符出现的位置要处理好......
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
char s[1005],t[1005],f[1005],s1[1005],t1[1005];
int dp1[1005][1005],dp2[1005][1005];
int cnts=0;
int cntt=0;
int lens,lent,lenf;
int deal(int beg[],int end[],char str[])
{
int len=strlen(str);
int flag=0;
for(int i=0;i<len;i++)
{
int p1=0,p2=i;
if(str[p2]==f[p1])
{
while(p1<lenf&&p2<len)
{
if(str[p2]==f[p1])
{
p1++;
p2++;
}
else
p2++;
}
if(p1==lenf)
{
beg[flag]=i;
end[flag++]=p2-1;
}
}
}
return flag;
}
int main()
{
int text,kp=0;
scanf("%d",&text);
while(text--)
{
scanf("%s",s);
scanf("%s",t);
scanf("%s",f);
memset(dp1,0,sizeof(dp1));
memset(dp2,0,sizeof(dp1));
lens=strlen(s);
lent=strlen(t);
lenf=strlen(f);
for(int i=1;i<=lens;i++)
{
for(int j=1;j<=lent;j++)
if(s[i-1]==t[j-1])
dp1[i][j]=dp1[i-1][j-1]+1;
else
{
int maxx=0;
if(maxx<dp1[i-1][j])
maxx=dp1[i-1][j];
if(maxx<dp1[i][j-1])
maxx=dp1[i][j-1];
dp1[i][j]=maxx;
}
}
int cnt=0;
for(int i=lens-1;i>=0;i--)
s1[cnt++]=s[i];
cnt=0;
for(int i=lent-1;i>=0;i--)
t1[cnt++]=t[i];
for(int i=1;i<=lens;i++)
{
for(int j=1;j<=lent;j++)
if(s1[i-1]==t1[j-1])
dp2[i][j]=dp2[i-1][j-1]+1;
else
{
int maxx=0;
if(maxx<dp2[i-1][j])
maxx=dp2[i-1][j];
if(maxx<dp2[i][j-1])
maxx=dp2[i][j-1];
dp2[i][j]=maxx;
}
}
int beg1[1005],end1[1005];
int beg2[1005],end2[1005];
cnts=deal(beg1,end1,s);
cntt=deal(beg2,end2,t);
int maxn=0;
//for(int i=0;i<cnts;i++)
//printf("%d %d\n",beg1[i],end1[i]);
for(int i=0;i<cnts;i++)
for(int j=0;j<cntt;j++)
if(maxn<dp1[beg1[i]+1][beg2[j]+1]+dp2[lens-end1[i]-1][lent-end2[j]-1]+lenf)
maxn=dp1[beg1[i]+1][beg2[j]+1]+dp2[lens-end1[i]-1][lent-end2[j]-1]+lenf;
printf("Case #%d: %d\n",++kp,maxn-1);
}
return 0;
}
6、uva10635
题意:给你两个数组,A和B,每个数组里面的元素是唯一的,求这两个数组的最长公共子序列........
思路:这个题目数据量很大,求最长公共子序列要o(n*n)会超时,其实可以转换为求最长递增子序列,然后可以在O(nlogn)的时间复杂度求解........
就是,给A中的元素一次编号,在输入B中元素的时候,先判断A中是否有这个元素,没有的话,直接过滤,有的话,把这个元素在A中的编号记录在B数组,然后对B求一次最长递增子序列.........
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int a[100000],b[100000],dp[100000];
int main()
{
int text,f=0;
scanf("%d",&text);
while(text--)
{
int n,p,q;
scanf("%d%d%d",&n,&p,&q);
memset(a,0,sizeof(a));
for(int i=1;i<=p+1;i++)
{
int tmp;
scanf("%d",&tmp);
a[tmp]=i;
}
int cnt=0;
for(int i=1;i<=q+1;i++)
{
int tmp;
scanf("%d",&tmp);
if(a[tmp]==0) continue;
b[cnt++]=a[tmp];
}
int len=1;
//for(int i=0;i<cnt;i++)
//printf("%d\n",b[i]);
dp[0]=b[0];
for(int i=1;i<cnt;i++)
{
int left=0,right=len-1,mid;
if(dp[len-1]<b[i])
dp[len++]=b[i];
else
{
right=len-1;
while(left<=right)
{
mid=(left+right)/2;
if(dp[mid]<b[i])
left=mid+1;
else right=mid-1;
}
dp[left]=b[i];
}
}
printf("Case %d: %d\n",++f,len);
}
return 0;
}
7、poj1952(求子序列个数)
题意:求最长递减(严格递减)子序列长度,并输出不相同的最长递减子序列的个数
思路:
题解:两次DP。首先求出最长的递减子序列的长度,状态转移方程为dp[i]=max(dp[j])+1;(0<=j<i);(下标从0开始)其中dp[i]表示以第i个数结尾的最长递减子序列的长度。这里在最后加一个dp[n]=-1,具体为什么在下一步讲述。
然后求每一种长度的递减子序列共有几个。状态转移方程:count[i]=sum(coun[j]);(其中a[i]<a[j]&&dp[j]+1==dp[i]);count[i]是以a[i]结尾的以dp[i]长度的递减子序列的个数。这里要注意排除子序列相同的情况!这里给出一个例子加以说明:
i 0 1 2 3 4 5 6
a[i] 5 8 4 4 3 2 -1
dp[i] 1 1 2 2 3 4 5
count[i] 1 1 2 2 2 2 2
这里在统计count[4]时,当加上count[3]后要忽略count[2]。
还有在数组最后加上a[n]=-1是保证count[n]一定是最终的答案,而不需要递推完count[]后再一一查找出最大的count[]中的最大值。
上面如果理解了dp[]和count[]的数组的意义之后此题的解法就很明了了。
代码:
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[5005],cont[5005],a[5005];
int main()
{
int n;
//scanf("%d",&text);
while(scanf("%d",&n)>0)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
a[n+1]=-100000;
for(int i=0;i<=n+1;i++)
{
dp[i]=1;
//cont[i]=1;
}
int maxn1=0;
for(int i=1;i<=n+1;i++)
{
for(int j=1;j<i;j++)
if(a[i]<a[j]&&dp[i]<dp[j]+1)
dp[i]=dp[j]+1;
if(dp[i]>maxn1)
maxn1=dp[i];
}
int flag=0;
for(int i=1;i<=n+1;i++)
{
if(dp[i]==1)
{
cont[i]=1;
continue;
}
else
cont[i]=0;
for(int j=i-1;j>=1;j--)
{
if(a[i]<a[j]&&dp[i]==dp[j]+1)
{
flag=1;
for(int k=j+1;k<i;k++)
{
if(a[k]==a[j])
{
flag=0;
break;
}
}
if(flag)
cont[i]+=cont[j];
}
}
}
printf("%d %d\n",maxn1-1,cont[n+1]);
}
return 0;
}