LIS && LCS

LCS

最长公共子序列,大神博客
在这里不再 一 一 赘述LCS,如果为小白请参见链接,
接下来谈一下LCS的路径回溯问题,大神的博客中已经提到了一种路径回溯的方法,我这里在提供一种以供选择
code:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <stack>

using namespace std;

const int maxn = 1e3+5;

int dp[maxn][maxn];
char a[maxn],b[maxn];

stack<char>vis;

int main(){
    scanf("%s",a+1);
    scanf("%s",b+1);
    memset(dp,0,sizeof(dp));
    int a_len = strlen(a+1),b_len = strlen(b+1);
    for (int i = 1;i<=a_len;i++) {
        for (int j = 1;j<=b_len;j++) {
            if ( a[i] == b[j] ) {
                dp[i][j] = dp[i-1][j-1] + 1;
            } else {
                dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
            }
        }
    }
    int i = a_len,j = b_len;
    while ( i > 0 || j > 0 ) {
        if ( dp[i-1][j] == dp[i][j]) {
            i--;
        } else if ( dp[i][j-1] == dp[i][j] ) {
            j--;
        } else {
            vis.push(a[i]);
            i--,j--;
        }
    }
    int len = vis.size();
    for (int i = 0;i<len;i++) {
        printf("%c ",vis.top());
        vis.pop();
    }
    return 0;
}

LIS

最长上升子序列, 大神博客
LIS重点是在第二种贪心加二分的求法,第三种着重掌握树状数组

第二种方法求LIS着重在于二分查找的写法
例题 P1020 导弹拦截

题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是 ≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
输入输出格式

输入格式:
1 行,若干个整数(个数 ≤100000)

输出格式:
2 行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入输出样例

输入样例#1:
389 207 155 300 299 170 158 65

输出样例#1:
6
2
AC code:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn = 1e5+5;
const int INF = 0x3f3f3f3f;
int v[maxn];
int low[maxn],high[maxn];
int pos1,pos2;

int bin_search1(int *a,int r,int x)
{
    int l=1,mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(a[mid]>=x)
            l=mid+1;
        else
            r=mid-1;
    }
    return l;
}

int bin_search2(int *a,int r,int x)
{
    int l=1,mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(a[mid]<x)
            l=mid+1;
        else 
            r=mid-1;
    }
    return l;
}

int main() {
    int s = 1;
    while(cin>>v[s]) s++;
    s--; pos1 = 1;pos2 = 1;
    memset(low,INF,sizeof(low)); memset(high,0,sizeof(high));
    high[pos1] = v[1];
    for (int i = 2;i<=s;i++) {
        if ( v[i]>high[pos1] ) {
            pos1++;
            high[pos1] = v[i];
        } else {
            high[bin_search2(high,pos1,v[i])] = v[i];
        }
    }
    low[1] = v[1];
    for (int i = 2;i<=s;i++) {
        if ( v[i]<=low[pos2] ) {
            pos2++;
            low[pos2] = v[i];
        } else {
            low[bin_search1(low,pos2,v[i])] = v[i];//可以直接使用内置函数lower_bound
        }
    } 
    printf("%d\n%d\n",pos2,pos1);
    return 0;

}

LIS还有一种经典的问法求上升子序列的个数

Longest Increasing Subsequence

描述
给出一组长度为nnn的序列,a1,a2,a3,a4…an求出这个序列长度为k的严格递增子序列的个数

输入
第一行输入T组数据 T(0≤T≤10)
第二行输入序列大小n(1≤n≤100),长度k(1≤k≤n)
第三行输入n个数字ai(0≤ai≤1e9)

输出
数据规模很大, 答案请对1e9+7取模

输入样例 1
2
3 2
1 2 2
3 2
1 2 3

输出样例 1
2
3

暴力深搜显然TLE,可是我们学习过LIS,LIS的dp状态是dp[i]表示以i结尾的最长上升子序列的长度
这个题我们可以稍稍变一型用dp[i][j]表示以i结尾j为长度的最长上升子序列的最大个数
AC code:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 110;
const int mod = (int)1e9+7;

int arr[maxn],dp[maxn][maxn];

int main(){
    int t; cin>>t;
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        int n,m; scanf("%d %d",&n,&m);
        for (int i = 1;i<=n;i++) {
            scanf("%d",&arr[i]);
            dp[i][1] = 1;
        }
        for (int i = 1;i<=n;i++) {
            for (int j = 2;j<=i;j++) {
                for (int k = 1;k<i;k++) {
                    if ( arr[i] > arr[k] ) {
                        dp[i][j] = (dp[i][j] + dp[k][j-1]) % mod;
                    }
                }
            }
        }
        int ans = 0;
        for (int i = 1;i<=n;i++) {
            ans = ( ans + dp[i][m] ) % mod;
        }
        printf("%d\n",ans);
    }
    return 0;
}
//LIS的dp_i是以i结尾的最长的子序列的程度 
//此题dp_i_j是以i结尾j为长度的子序列的数量

但是此题可以用树状数组维护写一个时间复杂度为 n*logn的算法,这里我们不讲

有兴趣的请看51nod 1376 最长递增子序列的数量
当然了LIS也有路径回溯考虑用链表来实现思考一下,但是这个的复杂度为O(n^2)因此大家可以自己想像一下
讲一种O(n*logn)的方法

#include <cstdio>//参见代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <stack>

using namespace std;

const int maxn = 1e5+5;

int dp[maxn],v[maxn],vir[maxn];

int main(){
    int t;
    while(~scanf("%d",&t) && t)
    {
        int pos = 0;
        for (int i = 1;i<=t;i++) { scanf("%d",&v[i]); dp[i] = 0; vir[i] = 0; }
        for (int i = 1;i<=t;i++) {
            if ( dp[pos] < v[i] ) { dp[++pos] = v[i] ; vir[i] = pos;  }
            else {
                int p = lower_bound(dp+1,dp+pos+1,v[i]) - dp;
                dp[p] = v[i]; v[i] = p;
            }
        }
        stack<int>st;
        for (int i = t;i>=1;i--) {
            if ( pos == vir[i] ) {
                st.push(i); pos--;
            } else if(pos <= 0) {
                break;
            }
        }
        while(!st.empty()) { printf("%d ",st.top()); st.pop(); }
        printf("\n");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Acer12138/article/details/81536765
今日推荐