1.引例:什么是公共子序列?就是两个或者多个串里最长的公共部分。注:公共部分元素可以不连续但是前后出现顺序一定相同!现在要求你编写一个程序,求出输入的两个串的最长公共子序列长度。
#include <iostream> #include<bits/stdc++.h> using namespace std; int numA[100]; int numB[100]; int DP[100][100]; int main() { int T; cin>>T; int t=0; while(T--) { t++; memset(DP,0,sizeof(DP)); int m,n; cin>>m>>n; for(int i=1;i<=m;i++) cin>>numA[i]; for(int i=1;i<=n;i++) cin>>numB[i]; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { if(numA[i]==numB[j]) { DP[i][j]=DP[i-1][j-1]+1; } else DP[i][j]=DP[i-1][j]>DP[i][j-1]?DP[i-1][j]:DP[i][j-1]; } } cout<<"Case "<<t<<": "<<DP[m][n]<<endl; } return 0; }
*核心代码:
for(int i=1;i<=m;i++) { int save=DP[0]; for(int j=1;j<=n;j++) { int k=DP[j];//记录当前位置 if(numA[i]==numB[j])DP[j]=save+1;//可以随便更新,若是相等,save就是对角线的原本位置 else DP[j]=DP[j]>DP[j-1]?DP[j]:DP[j-1];//否则取较大的 save=k;//保存 } }*代码:
#include <iostream> #include<cstring> using namespace std; int DP[100]; int numA[100]; int numB[100]; int main() { int T; cin>>T; int t=0; while(T--) { memset(DP,0,sizeof(DP)); t++; int m,n; cin>>m>>n; for(int i=1;i<=m;i++) { cin>>numA[i]; } for(int i=1;i<=n;i++) { cin>>numB[i]; } for(int i=1;i<=m;i++) { int save=DP[0]; for(int j=1;j<=n;j++) { int k=DP[j]; if(numA[i]==numB[j])DP[j]=save+1; else DP[j]=DP[j]>DP[j-1]?DP[j]:DP[j-1]; save=k; } } cout<<"Case "<<t<<": "<<DP[n]<<endl; } return 0; }
从这个图中我们就可以看出,在我们求解最大数目的时候,每一个相同元素处的路径转折如图深色部分,所以如果我们想求出其一个解,只要按照来的方向回溯就可以了,回溯规则是:
#include <iostream> #include<bits/stdc++.h> using namespace std; int DP[100][100]; int mark[100][100]; int numA[100]; int numB[100]; int output[100]; int main() { int T; cin>>T; while(T--) { memset(DP,0,sizeof(DP)); int n,m; cin>>n>>m; for(int i=1; i<=n; i++) { cin>>numA[i]; } for(int j=1; j<=m; j++) { cin>>numB[j]; } for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(numA[i]==numB[j]) { 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]=2; } } } int k=DP[n][m]; cout<<"The numbers of max bares is "<<DP[n][m]<<"."<<endl; int i=n,j=m; while(i&&j)//循环法 { if(mark[i][j]==0) { output[k--]=numA[i]; i--; j--; } else if(mark[i][j]==1) { i--; } else j--; } for(int p=1;p<=DP[n][m];p++) { if(p==1) cout<<output[p]; else cout<<" "<<output[p]; } cout<<endl; } return 0; }
#include <iostream> #include<bits/stdc++.h> using namespace std; int DP[100][100]; int mark[100][100]; int numA[100]; int numB[100]; void output(int a,int b) { if(!a||!b)return; if(mark[a][b]==0) { output(a-1,b-1); cout<<numA[a]<<" "; } else if(mark[a][b]==1) output(a-1,b); else output(a,b-1); } int main() { int T; cin>>T; while(T--) { memset(DP,0,sizeof(DP)); int n,m; cin>>n>>m; for(int i=1; i<=n; i++) { cin>>numA[i]; } for(int j=1; j<=m; j++) { cin>>numB[j]; } for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(numA[i]==numB[j]) { 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]=2; } } } cout<<"The numbers of max bares is "<<DP[n][m]<<"."<<endl; output(n,m); cout<<endl; } return 0; }
#include <iostream> #include<bits/stdc++.h> using namespace std; int dp[1000]; int num[1000]; int main() { int n; memset(dp,0,sizeof(dp)); cin>>n; for(int i=0;i<n;i++) { cin>>num[i]; dp[i]=1;//初始化为1 } int maxn=0;//记录最大长度 for(int i=0;i<n;i++)//枚举结束点 { for(int j=0;j<i;j++)//在结束点之前找条件 { if(num[j]<num[i]) { dp[i]=dp[i]>dp[j]+1?dp[i]:dp[j]+1;//更新 } } maxn=maxn>dp[i]?maxn:dp[i];//更新最大值,此时最大值并不一定是DP[n]了; } cout<<maxn<<endl; return 0; }
1、在数据量比较大的时候n^2会明显超时,所以可以使用nlogn 的算法,此算法少了双重循环,用的lower_bound(二分法)。
2、lis中的数字并没有意义,仅仅是找到最小点lis[0]和最大点lis[len],其中,在大于lis[len]时len++,在小于lis[len]时可以将arr[i]在lis中的数进行替换掉。所以此算法主要是在不停的找最合适的起点和最合适的终点。
引用:
假设存在一个序列d[1..9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。n
下面一步一步试着找出它。
我们定义一个序列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)~!!
for(int i=0;i<n;i++) { if(num[i]>dp[len])//大于结尾,则++len,并且存入 dp[++len]=num[i]; else//否则找大于等于它的第一个点插入,更新 { int pos=lower_bound(dp,dp+len,num[i])-dp; dp[pos]=num[i]; } }(3)代码:
#include <iostream> #include<bits/stdc++.h> using namespace std; int num[1000]; int dp[1000]; int main() { int n; cin>>n; for(int i=0;i<n;i++) { cin>>num[i]; } dp[0]=num[0]; int len=0; for(int i=0;i<n;i++) { if(num[i]>dp[len]) dp[++len]=num[i]; else { int pos=lower_bound(dp,dp+len,num[i])-dp;//STL里面的lower_bound()返回大于等于num[i]的第一个元素位置迭代器 dp[pos]=num[i]; } } cout<<len+1<<endl;//因为从0开始的 return 0; }
(4)上面(3)只能求总的最长,我想知道每一个数字为结尾时的每个数字处最长长度,可以维护一个increase[],在连接插入的同时维护即可!原理一样:
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; int a[10005]; int increase[10005]; int dp[10005]; int main() { int n; while(cin>>n) { for(int i=0; i<n; i++) { cin>>a[i]; increase[i] = 1;//每一个位置初始长度为自己 1 } dp[0] = a[0];//dp数组维护最长序列 int len = 0;//最长长度 for(int i=0; i<n; i++){ if(a[i]>dp[len]){//如果a[i]>dp[len],则尾部更新,len更新 dp[++len] = a[i]; increase[i] = len+1;//该处的最长 = len+1(因为len = 0开始) } else{ int pos=lower_bound(dp,dp+len,a[i]) - dp;//否则找尽量小的可插入位置 if(pos>=0){ dp[pos] = a[i]; increase[i] = pos+1;//该处的最长 = pos +1 } } } for(int i=0;i<len;i++) cout<<increase[i]<<" "; cout<<endl; cout<<len+1<<endl; } return 0; }
例题:uva10534 Wavio squence
题意:求一个波浪形的最长子序列,方法就是求每一个数字的上升子序列长度和下降子序列长度(逆向求上升子序列),任何枚举判断哪个大即可!必须用二分维护!
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; int a[10005]; int increase[10005]; int decrease[10005]; int dp[10005]; int main() { int n; while(cin>>n) { for(int i=0; i<n; i++) { cin>>a[i]; increase[i] = decrease [i] = 1; } dp[0] = a[0]; int len = 0; for(int i=0; i<n; i++){ if(a[i]>dp[len]){ dp[++len] = a[i]; increase[i] = len+1; } else{ int pos=lower_bound(dp,dp+len,a[i]) - dp; if(pos>=0){ dp[pos] = a[i]; increase[i] = pos+1; } } } // for(int i=0;i<n;i++) // cout<<increase[i]<<" "; // cout<<endl; dp[0] = a[n-1]; int len2 = 0; for(int i=n-1;i>=0;i--){ if(a[i]>dp[len2]){ dp[++len2] = a[i]; decrease[i] = len2+1; } else{ int pos=lower_bound(dp,dp+len2,a[i]) - dp; if(pos>=0){ dp[pos]= a[i]; decrease[i] = pos + 1; } } } // for(int i=0;i<n;i++) // cout<<decrease[i]<<" "; // cout<<endl; int maxx = 1; for(int i=0;i<n;i++){ maxx = max(maxx,2*min(increase[i],decrease[i]) - 1);//全部判断 } cout<<maxx<<endl; } return 0; }
#include<iostream> #include<bits/stdc++.h> using namespace std; int numA[251*251]; int numB[251*251]; int dp[251*251]; int main() { int T; cin>>T; int t=0; while(T--) { memset(numA,0,sizeof(numA)); memset(numB,0,sizeof(numB)); t++; int n,p,q; cin>>n>>p>>q; for(int i=1;i<=p+1;i++) { int a; cin>>a; numA[a]=i;//记录每个数字的位置编号(a的编号为i) } for(int i=1;i<=q+1;i++) { int a; cin>>a; numB[i]=numA[a];//将B里面的数字相对于A的编号放入B } int len=0; dp[len]=numB[1]; for(int i=2;i<=q+1;i++)//二分求LIS { if(numB[i]>dp[len]) dp[++len]=numB[i]; else { int pos=lower_bound(dp,dp+len,numB[i])-dp; dp[pos]=numB[i]; } } cout<<"Case "<<t<<": "<<len+1<<endl;//注意len从0开始! } return 0; }
int maxlen=0;//复杂度n3 for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { if(numA[i]!=numB[j]) dp[i][j]=dp[i-1][j]; else { for(int k=1;k<j;k++)//遍历找到最大dp[i-1][0~j-1] { if(numB[j]>numB[k]&&dp[i][j]<dp[i-1][k]) dp[i][j]=dp[i-1][k]; } dp[i][j]++;//加一,加上当前相等 } maxlen=max(maxlen,dp[i][j]); } }
核心代码:
int maxlen=0; for(int i=1;i<=n;i++) { int maxn=0; for(int j=1;j<=m;j++) { dp[i][j]=dp[i-1][j]; if(numB[j]<numA[i]&&dp[i-1][j]>maxn)//this two can't take place toghter! maxn=dp[i-1][j]; else if(numA[i]==numB[j]) dp[i][j]=maxn+1; if(dp[i][j]>maxlen) maxlen=dp[i][j]; } }
代码:
#include <iostream> //#include<bits/stdc++.h> #include<cstring> #include<vector> using namespace std; int dp[1000][1000]; int pre[1000][1000];//保存路径 int numA[1000]; int numB[1000]; int main() { int T; cin>>T; while(T--) { memset(dp,0,sizeof(dp)); memset(pre,-1,sizeof(pre)); int n,m; cin>>n; for(int i=1; i<=n; i++) { cin>>numA[i]; } cin>>m; for(int i=1; i<=m; i++) { cin>>numB[i]; } for(int i=1; i<=n; i++) { int maxn=0,pa=0; for(int j=1; j<=m; j++) { dp[i][j]=dp[i-1][j]; if(numB[j]<numA[i]&&dp[i-1][j]>maxn)//this two can't take place toghter! { maxn=dp[i-1][j]; pa=j;//更新pa } else if(numA[i]==numB[j]) { dp[i][j]=maxn+1; pre[i][j]=pa;//保存pa } } } int maxlen=0,e; for(int i=1;i<=m;i++) { if(maxlen<dp[n][i]) { maxlen=dp[n][i]; e=i; } } vector<int> num; cout<<maxlen<<endl; for(int i=n;i>=1;i--) { if(pre[i][e]!=-1) { num.push_back(numA[i]); e=pre[i][e]; } } for(int i=num.size()-1;i>=0;i--) { if(i!=0) cout<<num[i]<<" "; else cout<<num[i]; } cout<<endl; } return 0; }