CodeForces - 10D LCIS

LCIS

+传送门+


◇题意◇

给定 n,m n , m ,并给定长度分别为 n,m n , m 的两个数组 a[],b[] a [ ] , b [ ] ,输出 a,b a , b 数组的最长公共子序列的长度和它本身。

n,m(1n,m500) n , m ( 1   ≤ n , m   ≤   500 )
听说 O(n4) O ( n 4 ) 都能过


◇经典 Dp D p

(如果有兴趣的话可以回忆一下更基础更经典的类似题)
最长上升子序列
最长公共子序列

单纯的我

摆出上面两道题跟这道题还是有一定关系的,但是为了不对大家产生误导,我先谈谈我做这道题的经过。
看到这道题的名字,先瞬间分解成了:

“最长上升子序列”+“最长公共子序列”。

于是我用取出“最长公共子序列”取出公共子序列,再用“最长上升子序列”的方法求出答案。
乍一眼看很有道理啊,但经过几次“ Wonderful W o n d e r f u l Answer A n s w e r ”后
发现在取出时,我们并不能保证取出的“最长公共子序列”包含“最长公共上升子序列”。

Example
a: a : 7 7 1 1 5 5 6 6 4 4 2 2 7 7
b: b : 7 7 1 1 5 5 4 4 6 6 7 7 2 2
按照递归的取“最长公共子序列”,取出:
7 7 1 1 5 5 6 6 2 2
此序列的“最长上升子序列”为:
1 1 5 5 6 6 (len=3)
但原序列的“最长公共上升子序列”为:
1 1 5 5 6 6 7 7 (len=4)

所以这就不对了。

基本思路

说句大实话,我真的不知道 O(n4) O ( n 4 ) 怎么写,所以思路从 O(n3) O ( n 3 ) 开始。
我们(根据以往经验)知道这道题中需要两个变量 i,j i , j a[],b[] a [ ] , b [ ] 中的下标)
好吧,直接说( dp d p 怎么想出来的貌似不太好说清楚)

状态定义:

f[i][j]: f [ i ] [ j ] : a[1...i] a [ 1... i ] 中以 b[j] b [ j ] 结尾的“最长公共子序列”的长度

状态转移:

f[i][j]={f[i1][j]max(f[i1][k])+1a[i]b[j]a[i]=b[j] f [ i ] [ j ] = { f [ i − 1 ] [ j ] a [ i ] ≠ b [ j ] max ( f [ i − 1 ] [ k ] ) + 1 a [ i ] = b [ j ]

1in,1jm,1k<j 1 ≤ i ≤ n , 1 ≤ j ≤ m , 1 ≤ k < j

解释一下

a[i]!=b[j] a [ i ] ! = b [ j ] i i 之前的以 b[j] b [ j ] 结尾的序列自然没有改变,仍然是长度仍然是 f[i1][j] f [ i − 1 ] [ j ] ;
a[i]=b[j] a [ i ] = b [ j ] :有一个新的公共元素 b[j] b [ j ] ,把它放到序列尾部,固枚举 k k 求出之前最长的序列并加上去 (b[k]<b[j]) ( b [ k ] < b [ j ] )

差不多了。


◇代码和更进一步◇

O(n3) O ( n 3 )
思路如上。
代码如下。

(我真是充满诗性)
(Dp后的递归输出如有疑问,请参见 Dp D p 后的递归输出)

#include
#include
using namespace std;
const int MAXN=int(5e2+5);

int l1,l2;
int f[MAXN][MAXN];
int a[MAXN],b[MAXN];

void Print(int x,int y) {
    if(f[x][y]==1) {
        printf("%d ",b[y]);
        return;
    }
    if(x==0||y==0)
        return;
    if(f[x][y]==f[x-1][y]) {
        Print(x-1,y);
        return;
    }
    for(int i=y-1;i>=1;i--)
        if(b[i]1][i]+1) {
            Print(x,i);
            printf("%d ",b[y]);
            return;
        }
}

int main()
{
    int ans=0,a1,b1;
    scanf("%d",&l1);
    for(int i=1;i<=l1;i++)
        scanf("%d",&a[i]);
    scanf("%d",&l2);
    for(int i=1;i<=l2;i++)
        scanf("%d",&b[i]);
    for(int i=1;i<=l1;i++)
        for(int j=1;j<=l2;j++) {
            if(a[i]!=b[j])
                f[i][j]=f[i-1][j];
            else {
                int Max=0;
                for(int k=1;kif(b[k]1][k]);
                f[i][j]=Max+1;
                if(ansprintf("%d\n",ans);
    if(ans)
        Print(a1,b1);
}
O(n2) O ( n 2 )
思路

我们看一下上面那篇代码中,让我们把复杂度升到让人惊恐( 5003 500 3 不太慌)的 O(n3) O ( n 3 ) 是什么。
对对对,除了必要的 i|1in,j|1jm i | 1 ≤ i ≤ n , j | 1 ≤ j ≤ m 的枚举外,还有转移时的枚举 k|1k<j k | 1 ≤ k < j
所以,考虑从 k k 入手优化。
看一下 k k 都做了什么:枚举 max(f[i1][1..(j1)]) m a x ( f [ i − 1 ] [ 1.. ( j − 1 ) ] )
但仔细一想,这是没有必要的,注意下面这两段循环。

这里写图片描述

可以看到,在 a[i]=b[j] a [ i ] = b [ j ] 进入 k k 的循环之前,我们已经扫描过一次所有的 k k 了(在当前 j j 之前的 j" “ j " ),即所有的 f[i1][k] f [ i − 1 ] [ k ] 都在本层 i i 的循环中可以被扫描。
故我们可以在 i,j i , j 的循环中找到 max(f[i1][k]) m a x ( f [ i − 1 ] [ k ] )
这里我们要满足一个条件:
这里写图片描述

怎么在 i i 循环中满足当前未知的 j j 呢?
很容易的,观察一下进入 k k 循环的条件:
这里写图片描述

所以
这里写图片描述

其他操作都一样了。

代码如下
#include
#include
using namespace std;
const int MAXN=int(5e2+5);

int l1,l2;
int f[MAXN][MAXN];
int a[MAXN],b[MAXN];

void Print(int x,int y) {
    if(f[x][y]==1) {
        printf("%d ",b[y]);
        return;
    }
    if(x==0||y==0)
        return;
    if(f[x][y]==f[x-1][y]) {
        Print(x-1,y);
        return;
    }
    for(int i=y-1;i>=1;i--)
        if(b[i]1][i]+1) {
            Print(x,i);
            printf("%d ",b[y]);
            return;
        }
}

int main()
{
    int ans=0,a1,b1;
    scanf("%d",&l1);
    for(int i=1;i<=l1;i++)
        scanf("%d",&a[i]);
    scanf("%d",&l2);
    for(int i=1;i<=l2;i++)
        scanf("%d",&b[i]);
    for(int i=1;i<=l1;i++) {
        int Max=0;
        for(int j=1;j<=l2;j++) {
            if(a[i]!=b[j])
                f[i][j]=f[i-1][j];
            if(a[i]>b[j]&&Max1][j])
                Max=f[i-1][j];
            if(a[i]==b[j]) {
                f[i][j]=Max+1;
                if(f[i][j]>ans)
                    ans=f[i][j],a1=i,b1=j;
            }
        }
    }
    printf("%d\n",ans);
    if(ans)
        Print(a1,b1);
}

O(n2) O ( n 2 ) ′
如果你没有戳开上文的 Dp D p 后的递归输出
再给一次机会!!!戳它 Dp D p 后的递归输出
好吧,如果你再次选择了拒绝。
下面的代码或许会带来些帮助。
接下来要讲的是

用动态数组储存 Dp D p 中的路径

其实,这样的做法,跟上面的做法本质上相同,故思路就不在重讲。
具体实现是开一个 vector<int>G[] v e c t o r < i n t > G [ ]
G[] G [ ] 存储路径, G.size() G . s i z e ( ) 表示长度,转移直接对 G[] G [ ] 进行操作就行了。
但这个在空间和时间上,都没上一种优秀。
但胜在简单,直观。

代码

对照上一篇看

#include
#include
#include
using namespace std;
const int MAXN=int(5e2+5);

int l1,l2;
int a[MAXN],b[MAXN];
int tap;
vector<int>ans[MAXN],ans1;

int main()
{
    scanf("%d",&l1);
    for(int i=1;i<=l1;i++)
        scanf("%d",&a[i]);
    scanf("%d",&l2);
    for(int i=1;i<=l2;i++)
        scanf("%d",&b[i]);
     for(int j=1;j<=l2;j++) {
        ans1.clear();
        for(int i=1;i<=l1;i++) {
            if(b[j]>a[i]&&ans[i].size()>ans1.size()) 
                ans1=ans[i];
            if(b[j]==a[i]) {
                ans[i]=ans1;
                ans[i].push_back(a[i]);
            }
        }
    }
    for(int i=1;i<=l1;i++)
        if(tap==0||ans[i].size()>ans[tap].size())
            tap=i;
    printf("%d\n",ans[tap].size());
    for(int i=0;iprintf("%d ",ans[tap][i]);
}

再说两句

考试的时候,有位同学 O(n4) O ( n 4 ) 比我的 O(n2) O ( n 2 ) 测出来还要快(或许是我太菜),这告诉我们

考试千万别放弃
能写暴力就暴力

to t o be b e or o r not n o t to t o be b e , this t h i s is i s a a question q u e s t i o n .


Thanks T h a n k s for f o r reading r e a d i n g !

你可能感兴趣的:(查来查去写笔记,考来考去碰运气)