LCS,LIS,LCIS
2013-08-04 10:32
429 查看
网站:CSUST 8月3日(LCS,LIS,LCIS)
LCS: 以下讲解来自:http://blog.csdn.net/yysdsyl/article/details/4226630
【问题】 求两字符序列的最长公共字符子序列
问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:
(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。
求解:
引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i]
= Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。
问题的递归式写成:
回溯输出最长公共子序列过程:
接下来放一个输出最长公共子序列的长度及序列的代码:
接下来是这一次比赛的题目:
A 大意是:求最长公共子序列。 Common Subsequence HDU 1159 62MS
B 大意是:求回文字符串要不的个数。方法:把字符串a,反序得b,在把a,b的最长公共子序列求出来,用a的长度n-最长公共子序列的长度。
Palindrome HDU 1513 484MS
先是代码,再是我的吐槽。
本来我的dp是开的dp[5001][5001],本来在POJ是没有超内存的,因为在POJ的内存限制的60000+,但是HDU和我们的比赛是32000+.......少了一半多,开dp[5001][5001]的内存是49000+,所以超内存了。但是因为在扫描的时候,每次只扫描两行,所以dp[i][j]就可以变为dp[i%2][j],所以dp[5001][5001]就可以改为dp[2][5002],这样内存就大大减小了。 本来我认为i,j可以分别%2,因为实际扫的时候只有关4个点:1自己所在的点,2右边的点,3下面的点,4斜下方的点。但是现在还没有找到可行的方法。
C 魔法串 HDU 4545
先是非最长公共子序列的代码: 0ms
再是求最长公共子序列的代码: 218MS
D 大意是:求最少有几个下降序列 最少拦截系统 HDU 1257 有个说法是求最长上升子序列的长度。
先是DP: 15MS 来自:http://blog.csdn.net/a_eagle/article/details/7237067
再是贪心: 46MS
同E一样的方法......三观尽毁..... 15MS
E 大意是:有N个娃娃,有自己高度和大小,小的可以放到大娃娃里,求最后剩下几个娃娃。 Nested Dolls HDU 1677
代码: 78MS
下面介绍另一种方法: 468MS
插花一下,先讲下最长上升子序列。
另一种方法:
E 大意是求最长公共上升子序列。 15ms
F 大意是:求最长回文上升子序列的长度。 0MS
LCS: 以下讲解来自:http://blog.csdn.net/yysdsyl/article/details/4226630
【问题】 求两字符序列的最长公共字符子序列
问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:
(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。
求解:
引进一个二维数组c[][],用c[i][j]记录X[i]与Y[j] 的LCS 的长度,b[i][j]记录c[i][j]是通过哪一个子问题的值求得的,以决定搜索的方向。
我们是自底向上进行递推计算,那么在计算c[i,j]之前,c[i-1][j-1],c[i-1][j]与c[i][j-1]均已计算出来。此时我们根据X[i]
= Y[j]还是X[i] != Y[j],就可以计算出c[i][j]。
问题的递归式写成:
回溯输出最长公共子序列过程:
接下来放一个输出最长公共子序列的长度及序列的代码:
#include <iostream> #include <string> #include <string.h> using namespace std; int c[100][100]; int b[100][100]; int LCS_Length(string x,string y) { int m=x.length(); int n=y.length(); int i,j; memset(c,0,sizeof(c));//根据递归方程的第一种情况,先初始化数组c[][] for(i=1;i<=m;i++) for(j=1;j<=n;j++) {//递归方程case 2 if(x[i-1] == y[j-1]) { c[i][j]=c[i-1][j-1]+1; b[i][j]=1; //表示 } else if(c[i-1][j] >= c[i][j-1]) //下面是递归方程case3 { c[i][j]=c[i-1][j]; b[i][j]=2; //表示↑ } else { c[i][j]=c[i][j-1];; b[i][j]=3; //表示← } } return c[m] ; } void Print_LCS(string X,int i,int j)//输出最优解 { if( (i == 0) || (j == 0) ) return; if(b[i][j] == 1) { Print_LCS(X,i-1,j-1); cout<<X[i-1]<<" "; } else if(b[i][j] == 2) Print_LCS(X,i-1,j); else Print_LCS(X,i,j-1); } int main() { string X,Y; while(cin>>X>>Y) { int p=LCS_Length(X,Y); cout<<"这两个字符串的LCS为:"<<p<<endl; cout<<"该公共子序列为:"; Print_LCS(X,X.length(),Y.length()); cout<<endl; } return 0; }
接下来是这一次比赛的题目:
A 大意是:求最长公共子序列。 Common Subsequence HDU 1159 62MS
#include<stdio.h> #include<iostream> #include<string.h> #include<string> using namespace std; char a[1010],b[1010]; short dp[1010][1010]; int n,l1,l2; int maxx(int i,int j) { return i>j?i:j; } int main() { while(~scanf("%s %s",a,b)) { l1=strlen(a); //长度 l2=strlen(b); int i,j; memset(dp,0,sizeof(dp)); for(i=1;i<=l1;i++) for(j=1;j<=l2;j++) { if(a[i-1]==b[j-1]) dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=maxx(dp[i-1][j],dp[i][j-1]); } printf("%d\n",dp[l1][l2]); } }
B 大意是:求回文字符串要不的个数。方法:把字符串a,反序得b,在把a,b的最长公共子序列求出来,用a的长度n-最长公共子序列的长度。
Palindrome HDU 1513 484MS
先是代码,再是我的吐槽。
#include<stdio.h> #include<string.h> char a[5001],b[5001]; short dp[2][5001]; int maxx(int i,int j) { return i>j?i:j; } int main() { int n,j,i; while(~scanf("%d",&n)) { scanf("%s",a); memset(dp,0,sizeof(dp)); for(i=0;i<n;i++) //反序得到b b[i]=a[n-i-1]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) { if(a[i-1]==b[j-1]) dp[i%2][j]=dp[(i-1)%2][j-1]+1; else dp[i%2][j]=maxx(dp[(i-1)%2][j],dp[i%2][j-1]); } printf("%d\n",n-dp[n%2] ); } return 0; }
本来我的dp是开的dp[5001][5001],本来在POJ是没有超内存的,因为在POJ的内存限制的60000+,但是HDU和我们的比赛是32000+.......少了一半多,开dp[5001][5001]的内存是49000+,所以超内存了。但是因为在扫描的时候,每次只扫描两行,所以dp[i][j]就可以变为dp[i%2][j],所以dp[5001][5001]就可以改为dp[2][5002],这样内存就大大减小了。 本来我认为i,j可以分别%2,因为实际扫的时候只有关4个点:1自己所在的点,2右边的点,3下面的点,4斜下方的点。但是现在还没有找到可行的方法。
C 魔法串 HDU 4545
先是非最长公共子序列的代码: 0ms
#include <iostream> #include <cstdio> #include <cstring> #include <vector> using namespace std; #define N 1010 char a ,b ; vector<char>c[30]; int main() { int i,j,k,m,T,t=1; scanf("%d",&T); char x,y; while(T--) { scanf("%s%s",a,b); scanf("%d",&m); memset(c,0,sizeof(c)); while(m--) { cin>>x>>y; c[(int)(x-'a')].push_back(y); //可以变的记录下来 } for(i=0,k=0;a[k]&&b[i];i++) { if(b[i]==a[k]) //如果相等,两列都向前+1 { k++; continue; } for(j=c[b[i]-'a'].size()-1;j>=0;j--) if(c[b[i]-'a'][j]==a[k]) //不同的时候如果可以变得和a,一样则+1 { k++; break; } } printf("Case #%d: ",t++); if(k>=strlen(a)) printf("happy\n"); else printf("unhappy\n"); } return 0; }
再是求最长公共子序列的代码: 218MS
#include<iostream> #include<string.h> #include<string> using namespace std; int dp[1005][1005]; bool has[128][128]; //这个要开大一点 int maxi(int x,int y) { if(x>y) return x; else return y; } int main() { int i,j,t,m,count=0,len1,len2; char a,b; string str1,str2; cin>>t; while(t--) { count=count+1; cin>>str1>>str2; len1=str1.size(); len2=str2.size(); memset(dp,0,sizeof(dp)); memset(has,0,sizeof(has)); cin>>m; for(j=1;j<=m;j++) { cin>>a>>b; has[a][b]=1; } for(i=1;i<=len1;i++) for(j=1;j<=len2;j++) { if(str1[i-1]==str2[j-1]||has[str2[j-1]][str1[i-1]]==1) //或者可以变一样的~~~~~ dp[i][j]=dp[i-1][j-1]+1; else dp[i][j]=maxi(dp[i-1][j],dp[i][j-1]); } cout<<"Case #"<<count<<": "; if(dp[len1][len2]==len1) cout<<"happy"<<endl; else cout<<"unhappy"<<endl; } return 0; }
D 大意是:求最少有几个下降序列 最少拦截系统 HDU 1257 有个说法是求最长上升子序列的长度。
先是DP: 15MS 来自:http://blog.csdn.net/a_eagle/article/details/7237067
#include <stdio.h>//dp[i]表示第i个导弹飞过来时需要的最少拦截装置 #include <string.h> int main() { int n,i,j,max,h[10001],dp[10001]; while(~scanf("%d",&n)) { memset(dp,0,sizeof(dp));//初始化拦截装置都为0 max=-1; for(i=1;i<=n;i++) scanf("%d",&h[i]);//飞来的高度 for(i=1;i<=n;i++) for(j=i-1;j>=0;j--) if(h[i]>h[j]&&dp[i]<dp[j]+1)//如果在拦截中出现了非单调递减的 dp[i]=dp[j]+1; for(i=1;i<=n;i++) if(dp[i]>max) max=dp[i]; //取最大值 printf("%d\n",max); } return 0; }
再是贪心: 46MS
#include <iostream> #include <string> #include <algorithm> #define MAX 100000000 using namespace std; int height[10000]; int top; void arrange(int n) { int i; sort(height,height+top+1); for(i=0;i<=top;i++) if(height[i]>=n) { height[i]=n; break; } if(i==top+1)//引入新的导弹系统 { top++; height[top]=n; } } int main() { int t; while(cin>>t) { top=0; height[0]=MAX;//初始可以阻挡任何高度 int height; for(int i=0;i<t;i++) { cin>>height; arrange(height); } cout<<top+1<<endl; } }
同E一样的方法......三观尽毁..... 15MS
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; class Do { public: int x,id; }D[20000]; int main() { int n; while(~scanf("%d",&n)) { int i,minn; memset(D,0,sizeof(D)); for(i=0;i<n;i++) scanf("%d",&D[i].x); int number=0,j; for(i=0;i<n;i++) if(D[i].id==0) //找到最大的没有标记的 { minn=D[i].x; number++; for(j=i+1;j<n;j++) if(D[j].x<minn && D[j].id==0) //小于最小高度&&没有被标记 { D[j].id=1; minn=D[j].x; //更新最小高度 } } printf("%d\n",number); } return 0; }
E 大意是:有N个娃娃,有自己高度和大小,小的可以放到大娃娃里,求最后剩下几个娃娃。 Nested Dolls HDU 1677
代码: 78MS
#include <iostream> #include <algorithm> #include <stdio.h> using namespace std; struct node { int w,h; }; node a[20001]; bool cmp(node a,node b) { if(a.w!=b.w) //按W排序,接下来的就不用管W了,只要比h的大小 return a.w>b.w; return a.h<b.h; } int visit[20001]; int main() { int t; scanf("%d",&t); while(t--) { int m; cin>>m; for(int i=0;i<m;i++) scanf("%d %d",&a[i].w,&a[i].h); sort(a,a+m,cmp); //排序 int cnt=0; visit[cnt++]=a[0].h; for(int i=1;i<m;i++) { if(a[i].h>=visit[cnt-1]) //高度大于等于前面w最小的娃娃的h,则又开一个新的娃娃 visit[cnt++]=a[i].h; else { int l=0,r=cnt; while(l<r) //二分找到可以放进去的最小的娃娃 { int mid=(l+r)/2; if(visit[mid]>a[i].h)r=mid; else l=mid+1; } visit[l]=a[i].h; } } cout<<cnt<<endl; } return 0; }
下面介绍另一种方法: 468MS
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; class Dolls { public: int x,y,id; }D[20020]; bool comp(Dolls a,Dolls b) { if(a.x==b.x) return a.y<b.y; //同上 return a.x>b.x; } int main() { int t,n; scanf("%d",&t); while(t--) { int i,minn; scanf("%d",&n); memset(D,0,sizeof(D)); for(i=0;i<n;i++) scanf("%d%d",&D[i].x,&D[i].y); sort(D,D+n,comp); //排序 int number=0,j; for(i=0;i<n;i++) if(D[i].id==0) //找到W最大的没有标记的娃娃 { minn=D[i].y; number++; for(j=i+1;j<n;j++) if(D[j].y<minn && D[j].id==0) //高度《这个娃娃,并且没有被标记 { D[j].id=1; //标记 minn=D[j].y; //更新高度 } } printf("%d\n",number); } return 0; }
插花一下,先讲下最长上升子序列。
#include <stdio.h> #define MAX 100000 #define INF 100000000 int a[MAX],c[MAX],len; int find(int L,int R,int x) { if(L==R) return L; int mid=(L+R)>>1; if(c[mid]<x) return find(mid+1,len,x); //二分 else return find(L,mid,x); //二分 } int main() { int i,n,j; while(scanf("%d",&n)!=EOF) { for(i=0;i<n;i++) scanf("%d",&a[i]); len=0; c[0]=-INF; for(i=0;i<n;i++) { if(a[i]>c[len]) j=++len; else j=find(1,len,a[i]); c[j]=a[i]; } printf("%d\n",len); } }
另一种方法:
#include <stdio.h> #include <string.h> int main() { int n,a[1000],map[1000],i,j,maxx,number; while(~scanf("%d",&n)) { memset(map,0,sizeof(map)); memset(c,0,sizeof(c)); number =0; for(i=0;i<n;i++) scanf("%d",&a[i]); for(i=0;i<n;i++) if(map[i]==0) //找到第一个没被标记的 { map[i]=1; number++; //总数+1 maxx=a[i]; for(j=i+1;j<n;j++) if(map[j]==0&&a[j]<=maxx) //大于前面的数并且未被标记 { map[j]=0; maxx=a[j]; //更新最大数 } } printf("%d\n",number); } return 0; }
E 大意是求最长公共上升子序列。 15ms
#include<cstdio> #include<cstring> int f[1005],a[1005],b[1005],i,j,t,n1,n2,max; int main() { scanf("%d",&t); while(t--) { scanf("%d",&n1); for(i=1;i<=n1;i++) scanf("%d",&a[i]); scanf("%d",&n2); for(i=1;i<=n2;i++) scanf("%d",&b[i]); memset(f,0,sizeof(f)); for(i=1;i<=n1;i++) { max=0; for(j=1;j<=n2;j++) { if (a[i]>b[j]&&max<f[j]) max=f[j]; if (a[i]==b[j]) f[j]=max+1; } } max=0; for(i=1;i<=n2;i++) if (max<f[i]) max=f[i]; printf("%d\n",max); } }
F 大意是:求最长回文上升子序列的长度。 0MS
#include <cstdio> #include <cstring> using namespace std; int n,ans,a[201],dp[201]; inline void Max(int &a,const int b){if(b>a) a=b;} int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); memset(dp,0,sizeof dp); for(int i=0;i<n;i++) scanf("%d",&a[i]); ans=1; for(int k=n-1;k>=0;k--) { int mx=0; for(int i=0;i<=k;i++) { if(a[i]<a[k]) Max(mx,dp[i]); else if(a[i]==a[k]) dp[i]=mx+1; if(i<k) Max(ans,dp[i]*2); else Max(ans,dp[i]*2-1); } } printf("%d\n",ans); } return 0; }
相关文章推荐
- LCS/LIS/LCIS 模板总结
- 8.3 LIS LCS LCIS(完结了==!)
- LCS/LIS/LCIS 模板总结
- LCS(最长公共子序列)、LIS(最长上升子序列)、LCIS(最长公共上升子序列)
- LIS && LCS && LCIS && LPS && MCS模板
- LCS/LIS/LCIS
- LIS LCS LCIS (主要过一遍,重在做题)
- LCS LIS LCIS 算法
- 线性DP总结(LIS,LCS,LCIS,最长子段和)
- LIS && LCS && LCIS
- 线性DP总结(LIS,LCS,LCIS,最长子段和)
- 最长串那点事儿(lis,lcs,lcis)
- 最长子序列(LCS, LIS, LCIS)
- 动态规划不经典问题之LCS与LIS的终极合体——LCIS!
- LIS+LCS+LCIS
- LCS,LCIS,LIS模板
- UVa 10635(lcs转lis优化模板)王子和公主
- LCS, LIS, 线性空间LCS( hirschberg算法)
- LIS&&LCS
- UVA 10635 Prince and Princess (LCS优化转LIS)