zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",ans);
    41     }
    42     return 0;
    43 }
    View Code


  • 相关阅读:
    软件性能测试
    我为何转来博客园
    【5】查询练习:DISTINCT、Between...and...、in、order by、count
    第5章:pandas入门【3】汇总和计算描述
    【4】建点表,填点数
    【3】数据库三大设计范式
    【2】约束
    【1】基本操作
    第5章:pandas入门【2】基本功能
    第5章:pandas入门【1】Series与DataFrame
  • 原文地址:https://www.cnblogs.com/jie-dcai/p/4341277.html
Copyright © 2011-2022 走看看