最长公共上升子序列(未完成)
最长上升子序列
【题目描述】:
给定一个长度为\(N\)的数列,求数值严格单调递增的子序列的长度最长是多少
第一阶段
\(1\leq N \leq 1000\)
做法:我们可以考虑直接暴力求解。
\(Code\):
/*
by : Zmonarch
知识点 ; 最长上升子序列
做法: force
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std ;
const int kmaxn = 1e6 + 10 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int a[kmaxn] , n ;
int f[kmaxn] ;
signed main()
{
n = read() ;
for(int i = 1 ; i <= n ; i++) a[i] = read() ;
for(int i = 1 ; i <= n ; i++)
{
f[i] = 1 ;
for(int j = 1 ; j < i ; j++)
{
if(a[i] > a[j])
{
f[i] = max(f[i] , f[j] + 1) ;
}
}
}
int ans = 1 ;
for(int i = 1 ; i <= n ; i++) ans = max(f[i] , ans) ; //最长上升子序列不一定以n结尾
printf("%lld\n" , ans) ;
return 0 ;
}
第二阶段
\(1\leq N \leq 10^6\)
我们发现\(N\)有点大,上方\(O(N^2)\)做法会\(TLE\),为了防止这种情况的出现,我们考虑优化,显然\(O(N logN)\)对于\(10^6\)是可过的,那么我们可以考虑进行数据结构优化。那么我们也可以仔细的观察一下状态转移
\(f_{i} = max(f_j + 1 , f_i)\),
这个式子比较好,我看不出来任何单调性,所以考虑树状数组暴力平摊\(logN\)的复杂度去寻找\(f_j\)的最大值,
对于数据结构优化\(DP\),不需要什么脑子,全是套路,可以尝试着理解这种套路,自然也可以直接背过。
\(Code\):
/*
by : Zmonarch
知识点 ; 最长上升子序列
做法: 树状数组优化
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define int long long
#define lowbit(x) x&(-x)
using namespace std ;
const int kmaxn = 1e6 + 10 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , a[kmaxn] , f[kmaxn] ;
int v[kmaxn] ;
void update(int x , int val)
{
for(int i = x ; i <= n ; i += lowbit(i))
{
f[i] = max(f[i] , val) ;
}
}
int query(int x )
{
int ans = 0 ;
for(int i = x ; i ; i -= lowbit(i))
{
ans = max(ans , f[i]) ;
}
return ans ;
}
signed main()
{
n = read() ;
int ans = 0 ;
for(int i = 1 ; i <= n ; i++) a[i] = read() , v[i] = a[i] ;
sort(v + 1 , v + n + 1 ) ;
int len = (v + 1 , v + n + 1 ) - v ;
for(int i = 1 ; i <= n ; i++)
{
int p = lower_bound(v , v + len , a[i]) - v + 1 ;
ans = max(ans , query(p - 1) + 1) ;
update(p , query(p - 1) + 1 ) ;
}
printf("%lld\n" , ans) ;
return 0 ;
}
第三阶段
【题目描述】:
给定一个序列,初始为空。
现在我们将 \(1\) 到 \(N\) 的数字插入到序列中,每次将一个数字插入到一个特定的位置。
每插入一个数字,我们都想知道此时最长上升子序列长度是多少?
【输入格式】:
第一行一个整数 \(N\),表示我们要将 \(1\) 到 \(N\) 插入序列中。
第二行 \(N\) 个数字,第 \(k\) 个数字 \(X_k\),表示我们将 k 插入到位置 \(X_k\)。
【\(solution\)】:
考查点:平衡树+线段树,笔者不会平衡树
最长公共子序列
给定两个长度分别为\(N\)和\(M\)的字符串\(A\)和\(B\),求既是\(A\)的子序列又是\(B\)的子序列的字符串长度最长是多少。
第一阶段
\(n,m\leq 1000\)
发现\(n,m\)非常小,考虑直接\(force\)
/*
by : Zmonarch
知识点 ; 最长上升子序列
做法: force
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std ;
const int kmaxn = 1e3 + 10 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , m ;
int f[kmaxn][kmaxn] ;
char a[kmaxn] , b[kmaxn] ;
signed main()
{
n = read() , m = read() ;
cin >> a + 1 ;
cin >> b + 1 ;
/*for(int i = 1 ; i <= n ; i++) a[i] = read() ; 这里也是可以转化为
//int的,一样
//处理
for(int j = 1 ; j <= n ; j++) b[j] = read() ;*/
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= m ; j++)
{
if(a[i] == b[j])
{
f[i][j] = f[i - 1][j - 1] + 1 ;
}
else
{
f[i][j] = max(f[i - 1][j] , f[i][j - 1]) ;
}
}
}
printf("%lld\n" , f[n][m] ) ;
return 0 ;
}
第二阶段
【\(description\)】:
字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。
令给定的字符序列 \(X=x_0x_1…x_{m-1}\),序列 \(Y=y_0y_1…y_{k-1}\) 是 \(X\) 的子序列,存在 \(X\) 的一个严格递增下标序列 \(i_0,i_1,…,i_{k-1}\) ,使得对所有的 \(j=0,1,…,k-1\),有 \(x_{i_j} = y_j\)。
例如,\(X=ABCBDAB\),\(Y=BCDB\) 是 \(X\) 的一个子序列。
对给定的两个字符序列,求出他们最长的公共子序列长度,以及最长公共子序列个数。
输入格式
第 \(1\) 行为第 \(1\) 个字符序列,都是大写字母组成,以 . 结束。
第 \(2\) 行为第 \(2\) 个字符序列,都是大写字母组成,以 .结束。
注意,两个字符序列均不包含最后的 .。
输出格式
第 \(1\) 行输出上述两个最长公共子序列的长度。
第 \(2\) 行输出所有可能出现的最长公共子序列个数,答案可能很大,只要将答案对 \(100,000,000\) 求余即可。
数据范围
输入字符序列的长度都不超过 \(5000\)
对于此题,和上方的第三个阶段一样,笔者不会。 等会了的话,会来更新的。
最长公共上升子序列
【\(description\)】:
对于两个数列\(A\)和\(B\),如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了
数列$A$和数列$B$的长度均不超过$3000$.
解法1
\(force\)
首先我们可以先考虑出是否是公共子序列,然后看一下是否是上升的子序列,为啥先判断公共子序列呢?
可以看一下我们的判断。
1.先判断公共子序列
if(a[i] == b[j])
{
…… ……
if(a[l] < a[i])
{
…… ……
}
}
2.先判断上升子序列
if(a[i] < a[j] && b[i] < b[j])
{
……
if(a[l] == b[r])
{
……
}
}
然后你发现,第一种打字少。
那么其他细节也就不赘述了
【\(Code\)】:
/*
by : Zmonarch
知识点 ; 最长公共上升子序列
做法: force
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std ;
const int kmaxn = 3e3 + 10 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n ;
int a[kmaxn] , b[kmaxn] ;
int f[kmaxn][kmaxn] ;
signed main()
{
n = read() ;
for(int i = 1 ; i <= n ; i++) a[i] = read() ;
for(int j = 1 ; j <= n ; j++) b[j] = read() ;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
if(a[i] == b[j])
{
for(int l = 1 ; l <= i ; l++)
{
for(int r = 1 ; r <= j ; r++)
{
if(a[l] < a[i])
{
f[i][j] = max(f[i][j] , f[l][r] + 1) ;
}
}
}
}
}
}
int ans = 0 ;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
ans = max(ans , f[i][j]) ;
}
}
printf("%lld\n" , ans) ;
return 0 ;
}
理由的话,就先放一下,然后今晚上时间不多了 ,就只打码了
解法2
我们显然有一个三维的\(DP\)
/*
by : Zmonarch
知识点 ; 最长上升子序列
做法: force
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std ;
const int kmaxn = 1e6 + 10 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n ;
int a[kmaxn] , b[kmaxn] ;
signed main()
{
n = read() ;
for(int i = 1 ; i <= n ; i++) a[i] = read() ;
for(int j = 1 ; j <= n ; j++) b[j] = read() ;
int ans = 0 ;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
if(a[i] != b[j])
{
f[i][j] = f[i - 1][j] ;
}
else
{
for(int k = 0 ; k < j ; k++)
{
if(a[k] < a[i])
{
f[i][j] = max(f[i][j] , f[i - 1][k] + 1) ;
}
}
}
}
ans = max(ans , f[i][j]) ;
}
printf("%lld\n" , ans) ;
return 0 ;
}
解法3
\(O(n^2)\)显然应该可以过掉这道题了
/*
by : Zmonarch
知识点 ; 最长上升子序列
做法: force
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
//#define int long long加上就MLE
using namespace std ;
const int kmaxn = 3e3 + 10 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , a[kmaxn] , b[kmaxn] , ans ;
int f[kmaxn][kmaxn] ;
signed main()
{
n = read() ;
for(int i = 1 ; i <= n ; i++) a[i] = read() ;
for(int j = 1 ; j <= n ; j++) b[j] = read() ;
for(int i = 1 ; i <= n ; i++)
{
int maxv = 1 ;
for(int j = 1 ; j <= n ; j++)
{
f[i][j] = f[i - 1][j] ;
if(a[i] == b[j]) f[i][j] = max(f[i][j] , maxv) ;
if(a[i] > b[j]) maxv = max(maxv , f[i - 1][j] + 1) ;
}
}
for(int i = 1 ; i <= n ; i++)
{
ans = max(f[n][i] , ans) ;
}
printf("%d\n" , ans) ;
return 0 ;
}