zoukankan      html  css  js  c++  java
  • 关于数字序列匹配的一些问题(lis, lcs, lcis等)

     

    题目描述

    给出一个序列a, 求a的最长上升子序列

    输入输出格式

    输入格式:

    第一行是一个数n,

    接下来一行,每行为n个数

    输出格式:

    一个数,即最长上升子序列的长度

    朴素版的lis是O(N ^ 2)的做法,这里就不在给出;当数据大时很容易被卡,通过二分优化 + 贪心可以优化成为O(NlogN),首先介绍两个函数:

    lower_bound( )和upper_bound( )是利用二分查找的方法在一个有序的数组中进行查找的。

    当数组是从小到大时,

    lower_bound( begin,end,num):表示从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,找到数字在数组中的下标。

    upper_bound( begin,end,num):表示从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,找到数字在数组中的下标。

    当数组是从大到小时,我们需要重载lower_bound()和upper_bound();

    struct cmp{bool operator()(int a,int b){return a>b;}};

    lower_bound( begin,end,num,cmp() ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

    upper_bound( begin,end,num,cmp() ):从数组的begin位置到end-1位置二分查找第一个小于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。

    b[i] 表示长度为i的上升子序列的最后一个数的最小值,如果a[i] > b[i] 则显然子序列的长度加1;否则找到找到第一个比它大的值将其替换,最终可以找到lis的长度;

    /*大佬们可以手写二分...*/

    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long 
    #define INF 0x3f3f3f3f
    #define MAXN 1000010
    #define MAXM 5010
    
    inline int read()
    {
        int x = 0,ff = 1;char ch = getchar();
        while(!isdigit(ch))
        {
            if(ch == '-') ff = -1;
            ch = getchar();
        }
        while(isdigit(ch))
        {
            x = (x << 1) + (x << 3) + (ch ^ 48);
            ch = getchar();
        }
        return x * ff;
    }
    
    int n,ans = 1,a[MAXN],b[MAXN];
    
    int main()
    {
        n = read();
        for(int i = 1;i <= n;++i)
            a[i] = read();
        b[1] = a[1];
        for(int i = 2;i <= n;++i)
        {
            if(b[ans] < a[i]) b[++ans] = a[i];
            else b[lower_bound(b + 1,b + ans + 1,a[i]) - b] = a[i];
        }
        printf("%d
    ",ans);
        return 0;
    }

     手写二分如下:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define MAXX 301000
    
    int n;
    int a[MAXX], f[MAXX];
    int top = 0;
    
    void find(int k) {
        int left = 1, right = top;
        while(left + 1 < right) {
            int mid = (left + right) / 2;
            if(f[mid] >= k) {
                right = mid;
            }
            else if(f[mid] < k) left = mid;
        }
    //    cout << left << ' ' << right  << ' ' << top << endl;
        if(f[left] > k) f[left] = k;
        else if(f[right] > k && f[left] <= k) f[right] = k;
    }
    
    int main() {
        memset(f, 0, sizeof(f));
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            if(a[i] > f[top]) {
                f[++top] = a[i];
            }
            else if(a[i] < f[top]) {
                find(a[i]);
            }
        }
        printf("%d", top);
        return 0;
    }

    加强版:

    描述 Description
    有N个整数,输出这N个整数的最长上升序列、最长下降序列、最长不上升序列和最长不下降序列。
    输入格式 Input Format
    第一行,仅有一个数N。 N<=700000
    第二行,有N个整数。 -10^9<=每个数<=10^9
    输出格式 Output Format
    第一行,输出最长上升序列长度。
    第二行,输出最长下降序列长度。
    第三行,输出最长不上升序列长度。
    第四行,输出最长不下降序列长度。

    这岂不是很显然:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    #define ll long long 
    #define INF 0x3f3f3f3f
    #define MAXN 1000010
    #define MAXM 5010
    
    inline int read()
    {
        int x = 0,ff = 1;char ch = getchar();
        while(!isdigit(ch))
        {
            if(ch == '-') ff = -1;
            ch = getchar();
        }
        while(isdigit(ch))
        {
            x = (x << 1) + (x << 3) + (ch ^ 48);
            ch = getchar();
        }
        return x * ff;
    }
    
    int n,ans1,ans2,ans3,ans4,a[MAXN];
    int b1[MAXN],b2[MAXN],b3[MAXN],b4[MAXN];
    
    struct cmp{bool operator()(int a,int b){return a>b;}};
    
    int main()
    {
        n = read();
        for(int i = 1;i <= n;++i)
            a[i] = read();
        ans1 = ans2 = ans3 = ans4 = 1;
        b1[1] = b2[1] = b3[1] = b4[1] = a[1];
        for(int i = 2;i <= n;++i)
        {
            if(a[i] > b1[ans1]) b1[++ans1] = a[i];
            else b1[lower_bound(b1 + 1,b1 + ans1 + 1,a[i]) - b1] = a[i];
            if(a[i] < b2[ans2]) b2[++ans2] = a[i];
            else b2[lower_bound(b2 + 1,b2 + ans2 + 1,a[i],cmp()) - b2] = a[i];
            if(a[i] <= b3[ans3]) b3[++ans3] = a[i];
            else b3[upper_bound(b3 + 1,b3 + ans3 + 1,a[i],cmp()) - b3] = a[i];
            if(a[i] >= b4[ans4]) b4[++ans4] = a[i];
            else b4[upper_bound(b4 + 1,b4 + ans4 + 1,a[i]) - b4] = a[i];
        }
        printf("%d
    %d
    %d
    %d
    ",ans1,ans2,ans3,ans4);
        return 0;
    }

     补充:这篇博客都发好长时间了, 在补充一些这样的题吧。 

    最长公共子序列

    题目描述

    给出1-n的两个排列P1和P2,求它们的最长公共子序列。

    输入格式

    第一行是一个数n,

    接下来两行,每行为n个数,为自然数1-n的一个排列。

    输出格式

    一个数,即最长公共子序列的长度


    emmmmm, 看到这个我们应该很容易想到一个$O(n^2)$的做法, 设$f[i][j]$表示a序列匹配到i, b序列匹配到j的最大公共长度, 则转移显然有

    $$f[i][j] =egin{cases}max(f[i - 1][j], f[i][j - 1]) & a[i] ot= a[j] \f[i - 1][j - 1] + 1 &a[i] = a[j]\end{cases} $$

    当当前数字不相等时, 考虑继承前面的状态, 所以取max, 而相等时, 就可以从前面的状态更新过来。 但这道题$O(n^2)$的复杂度显然是过不去。 我们可以想到排列的性质, 一个序列1~n,不重不漏的每个数字都有, 我们不妨开一个数组表示每个数在序列a中出现的位置,这样我们遍历b中的每一个数字, 知道其在a中对应的值, 如果满足单调上升, 就证明这两个子序列可以匹配, 这样用转化成了我们著名的lis算法, 在$O(nlogn)$ 的复杂度中就可以求出

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long ll;
    const int INF = 0x3f3f3f3f;
    const int MAXN = 1e5 + 100;
    const int MAXM = 5e3 + 10;
    const double eps = 1e-5;
    
    template < typename T > inline void read(T &x) {
        x = 0; T ff = 1, ch = getchar();
        while (!isdigit(ch)) {
            if (ch == '-') ff = -1;
            ch = getchar();
        }
        while (isdigit(ch)) {
            x = (x << 1) + (x << 3) + (ch ^ 48);
            ch = getchar();
        }
        x *= ff;
    }
    
    template < typename T > inline void write(T x) {
        if (x == 0) {
            putchar('0');
            return ;
        }
        if (x < 0) putchar('-'), x = -x;
        static T tot = 0, ch[30];
        while (x) {
            ch[++tot] = x % 10 + '0';
            x /= 10;
        }
        while (tot) putchar(ch[tot--]);
    } 
    
    int n, ans, a[MAXN], b[MAXN], f[MAXN], vis[MAXN];
    
    int main() {
        read(n); 
        for (register int i = 1; i <= n; ++i) read(a[i]), vis[a[i]] = i;
        for (register int i = 1; i <= n; ++i) read(b[i]);
        for (register int i = 1; i <= n; ++i) {
            if (vis[b[i]] > f[ans]) f[++ans] = vis[b[i]];
            else {
                int k = lower_bound(f + 1, f + ans + 1, vis[b[i]]) - f;
                f[k] = vis[b[i]];
            }
        }
        write(ans);
        
         return 0;
    }

     这是一次模拟赛的题, 也是我A掉的一道题,   又是数字序列匹配的一道问题, 同样是使两个序列相同。 我们首先或许会想到,匹配a,b的最长公共子序列, 然后, 你就跑不过样例, 因为你操作的只有a数组, 你可以吧a子序列中不合法的数丢到队头或队尾,但b是不可以移动的。 那就导致b子序列中间的数字无法匹配。  其实这样我们的思路就很清晰了, 既然b动不了, 就不动它呗。 用a的子序列去匹配b的字串。 由于这两个序列依然是n的排列, 所以我们可以$O(n)$的求出。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long ll;
    //const int INF = 0x3f3f3f3f;
    const int MAXN = 2e5 + 100;
    //const int MAXM = 2e3 + 10;
    //const double eps = 1e-5;
    
    template < typename T > inline void read(T &x) {
        x = 0; T ff = 1, ch = getchar();
        while(!isdigit(ch)) {
            if(ch == '-') ff = -1;
            ch = getchar();
        }
        while(isdigit(ch)) {
            x = (x << 1) + (x << 3) + (ch ^ 48);
            ch = getchar(); 
        }
        x *= ff;
    } 
    
    template < typename T > inline void write(T x) {
        if(x == 0) {
            putchar('0');
            return ;
        }
        if(x < 0) putchar('-'), x = -x;
        static T ch[30], tot = 0;
        while(x) {
            ch[++tot] = x % 10 + '0';
            x /= 10; 
        } 
        while(tot) putchar(ch[tot--]);
    }
    
    int n, ans, a[MAXN], b[MAXN], vis[MAXN];
    
    int main() {
        freopen("sequence.in", "r", stdin);
        freopen("sequence.out", "w", stdout);
        read(n);
        for (register int i = 1; i <= n; ++i) {
            read(a[i]);
            vis[a[i]] = i;
        }
        a[n + 1] = 0;
        vis[0] = -1;
        for (register int i = 1; i <= n; ++i) read(b[i]);
        int i = 1;
        while (i <= n) {
            int cnt = 1;
            while (++i <= n + 1) {
                if (vis[b[i]] > vis[b[i - 1]]) ++cnt;
                else break; 
            }
            ans = max(ans, cnt); 
        }
        write(n - ans);
        return 0;
    } 
  • 相关阅读:
    【linux 爱好者群】程序猿的那些聊天记录
    开发技巧记录
    tcmalloc 内存分析
    mktime很慢就自己去实现一个吧
    bash变量常用技巧
    文本处理sed常用操作
    【TED】如何掌握你的自由时间
    vim粘贴代码问题
    工作方式的反思-20170319
    【one day one linux】find 用法详解小记
  • 原文地址:https://www.cnblogs.com/AK-ls/p/10522631.html
Copyright © 2011-2022 走看看