HDU 4512 最长公共上升子序列

各种序列复习:

(1)最长上升子序列。

1、这个问题用动态规划就很好解决了,设dp[i]是以第i个数字结尾的上升子序列的最长长度。那么方程可以是dp[i]=max(dp[j]+1)。(j<i)。复杂度为O(n^2);

2、另外有一个该经典问题的O(nlogn)算法。

  首先知道,当求dp[i]时,如果出现a[k]<a[j],而dp[k]=dp[j]时,应当优先选k吧。那么,既然每次选的都是较小,就可以把字符串按照dp[t]=k这个子序列长度分类。当同样dp[t]=k时,记录下该长度的最小的a[p],设为数组d[k]。注意到d数组是单调不减的。为什么呢?因为假设当前是长度p,记录的位置就为d[p],如果出现d[q]>d[p],q<p,干脆就让以d[p]结尾的子序代替前面的。

  于是有这个特点:

  A、d[1]<=d[2]<=.......

  那么,在每次更新dp[i]时,对于字符a[i],只需找出比它小的最大的k,使d[k]<a[i],不就可以了吗。然后更新dp[i]。对于查找,由于单调,很明显可以使用二分查找。

  对于一个比较巧妙的写法。

  

 1 #include <iostream>  
 2 #include <cstdio>  
 3 #include <cstring>  
 4 using namespace std;  
 5 const int N = 41000;  
 6 int a[N];       //a[i] 原始数据  
 7 int d[N];       //d[i] 长度为i的递增子序列的最小值  
 8   
 9 int BinSearch(int key, int* d, int low, int high)  
10 {  
11     while(low<=high)  
12     {  
13         int mid = (low+high)>>1;  
14         if(key>d[mid] && key<=d[mid+1])  
15             return mid;  
16         else if(key>d[mid])  
17             low = mid+1;  
18         else  
19             high = mid-1;  
20     }  
21     return 0;  
22 }  
23   
24 int LIS(int* a, int n, int* d)  
25 {  
26     int i,j;  
27     d[1] = a[1];  
28     int len = 1;        //递增子序列长度  
29     for(i = 2; i <= n; i++)  
30     {  
31         if(d[len]<a[i])  
32             j = ++len;  
33         else  
34             j = BinSearch(a[i],d,1,len) + 1;  
35         d[j] = a[i];  
36     }  
37     return len;  
38 }  
39   
40 int main()  
41 {  
42     int t;  
43     int p;  
44     scanf("%d",&t);  
45     while(t--)  
46     {  
47         scanf("%d",&p);  
48         for(int i = 1; i <= p; i++)  
49             scanf("%d",&a[i]);  
50         printf("%d\n",LIS(a,p,d));  
51     }  
52     return 0;  
53 } 
View Code

 

(2)最长公共子序列。

  设dp[i][j]是第一个字符串以第i个结尾,第二个字符串以第j个结尾的长度。

  那么就有dp[i][j]=max{dp[i-1][j],dp[i][j-1],dp[i-1][j-1]}。前两种情况针对a[i]!=a[j]的,后一种是针对相等的。

(3)最长公共上升子序列。

我介绍一种O(N(M^2))的算法,它将是我们向O(NM)进步的阶梯。我们设F[j]为必选择B[j]为末尾时的最长。。。子序列(懒得打),那么F[j] = Max{F[k]}+1,并且通过设置一个i变量来枚举A[i]。

 1 var
 2     f       : array[0..5000] of integer;
 3     ans     : integer;
 4 procedure work2();
 5 var
 6     i,j,k:integer;
 7 begin
 8     for i:= 1 to n do
 9         for j:= 1 to m do
10             if a[i] = b[j] then
11                 for k:= 0 to j-1 do
12                     if b[k] < b[j] then
13                         if f[j] < f[k]+1 then
14                             f[j]:=f[k]+1;
15     for i:= 1 to m do
16         if ans < f[i] then
17             ans:=f[i];
18 end;
View Code

此时我们把空间降到了一维。解释一下,k循环下面比较时,B[k]所对应的A[?]一定在A[i]以前,而k也小于j,这就保证了解的合法性。但注意到其中的k循环,这实际上是用来找最大值用的。那么我们想,为什么不把最大只保存起来呢?i循环没结束时不断更新这个k值就行了啊。那么下面的算法就出来了:

procedure work;
var
    i,j,k:integer;
begin
    for i:= 1 to n do
        begin
            k:=0;
            for j:= 1 to m do
                begin
                    if a[i] = b[j] then
                        if f[j] < f[k]+1 then
                            f[j]:=f[k]+1;
                    if a[i] > b[j] then
                        if f[k] < f[j] then
                            k:=j;                  // 更新新的k
                end;
        end;
    for i:= 1 to m do
        if ans < f[i] then
            ans:=f[i];
end; 

其实,对于上面的更新保存最大值的操作,为什么是可行的呢?

要知道,更新DP数组f是在a[i]==b[j]时才进行的,因为f数组定义的是以b[j]为结尾的最长序列。那么,由于是上升的,则必定是在结尾的字符之前b[k]<a[i]吧?那么,在扫描的过程中,就可以当满足

f[k] < f[j]时更新k了。


对于HDU 4512这道题,也就是最长公共上升子序列的模型。我以两个序列来模拟,一个顺序一个逆序。在代码中可以知道,对于以b[j]为结束序列,在最外层循环到i时,内层循环最多只能到n-i+1,为什么呢?因为两个序列是互逆的,当超出这个值时,它们之前的序列就可能交叉或重叠。当a[n-i+1]==b[j]&&n-不+1==j时,序列最长应该是奇数的。在求解过程中找出最长序列即可。
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 #include <cstring>
 5 
 6 using namespace std;
 7 
 8 int num1[210],num2[210];
 9 int f[210];
10 
11 int main(){
12     int T,n;
13     scanf("%d",&T);
14     while(T--){
15         scanf("%d",&n);
16         for(int i=1;i<=n;i++){
17             scanf("%d",&num1[i]);
18             num2[n-i+1]=num1[i];
19         }
20         memset(f,0,sizeof(f));
21         int ans=1;
22         for(int i=1;i<=n;i++){
23             int k=0;
24             for(int j=1;j<=n-i+1;j++){
25                 if(num2[i]==num1[j]){
26                     if(f[j] < f[k]+1)
27                     f[j]=f[k]+1;
28                     if(j==n-i+1)
29                     ans=max(ans,2*f[j]-1);
30                     else {
31                         ans=max(ans,2*f[j]);
32                     }
33                 }
34                 if(num2[i]>num1[j]){
35                     if(f[k]<f[j])
36                     k=j;
37                 }
38             }
39         }
40         printf("%d\n",ans);
41     }
42     return 0;
43 }
View Code

 



转载于:https://www.cnblogs.com/jie-dcai/p/4341277.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值