zoukankan      html  css  js  c++  java
  • 水题欢乐赛-套路

    序言

    套路是人类进步的阶梯,我将不惜一切代价套路学习!
    —— 费清澄

    恩恩真是太对了

    题目描述

    zqc是一只套路的犰♂
    zqc有一个套路题库,当然,他为了让这个套路题库不被发现,给题库加了密。这个题库有很多密码,你只有输入套路密码后,你才能看到题目,并且题目的质量和套路密码的长度成正比.根据你获取到的情报,套路密码为两个字符串的公共子序列,现在你想知道最长套路密码的长度,以便获得高质量的套路题目.

    对于所有的数据满足|S| ≤ 1e6 , |T| ≤ 1e3 , 字母均为小写字母

    其实就是求最长公共子序列啦.

    显然最长公共子序列有O(nm)的DP做法,但是显然,这里nm在1e9数量级,这个方法是行不通的.

    车到山前必有路,有路必有老司机,这种题目,要么是有比原来更加优秀的最长公共子序列求法,要么就要利用题目本身的性质.

    事实上,确实有这么一种不同于原来O(nm)的DP的算法,复杂度在O(nlogn)~O(nmlogn)之间.大致思路就是推广了一下两个串都是全排列时的情况,使其可以解决一般字符串的问题.

    定理

    设序列A长度为n,{A(i)},序列B长度为m,{B(i)},考虑A中所有元素在B中的序号,即A某元素在B的序号为{Pk1,Pk2,..},将这些序号按照降序排列,然后按照A中的顺序得到一个新序列,此新序列的最长严格递增子序列即对应为A、B的最长公共子序列。

    很明显的可以看出,这个算法的复杂度和原序列的字符稠密度有关,假如只有一个字符,那么复杂度就会退化到O(n²logn),但是如果全都不同,那就是O(nlogn)(此时就是全排列求LIS).

    那么平均复杂度(假设每种字符出现频率一样)大概就是O(nm/p*logn),其中p是n和m中都出现过的字符个数.

    #include <stdio.h>
    #include <ctype.h>
    #include <string.h>
    #include <iostream>
    #include <string>
    #include <math.h>
    #include <vector>
    #include <queue>
    #include <algorithm>
     
    using namespace std;
     
    const int maxn = 200000001 ;
    vector<int> location[26] ;
    int c[maxn] , d[maxn] ;
    char a[maxn] , b[maxn] ;
    inline int get_max(int a,int b) { return a > b ? a : b ; }
     
    //nlogn 求lcs
    int lcs(char a[],char b[])
    {
        int i , j , k , w , ans , l , r , mid ;
        for( i = 0 ; i < 26 ; i++) location[i].clear() ;
        for( i = strlen(b)-1 ; i >= 0 ; i--) location[b[i]-'a'].push_back(i) ;
        for( i = k = 0 ; a[i] ; i++)
        {
            for( j = 0 ; j < location[w=a[i]-'a'].size() ; j++,k++) c[k] = location[w][j] ;
        }
        d[1] = c[0] ; d[0] = -1 ;
        for( i = ans = 1 ; i < k ; i++)
        {
            l = 0 ; r = ans ;
            while( l <= r )
            {
                mid = ( l + r ) >> 1 ;
                if( d[mid] >= c[i] ) r = mid - 1 ;
                else l = mid + 1 ;
            }
            if( r == ans ) ans++,d[r+1] = c[i] ;
            else if( d[r+1] > c[i] ) d[r+1] = c[i] ;
        }
        return ans ;
    }
     
    int main()
    {
        freopen("string.in","r",stdin);
        freopen("string.out","w",stdout);
        while (~scanf("%s%s",a,b))
        {
            printf("%d
    ",lcs(a,b));
        }
    }
    View Code

    如果不开Subtask的话,可以通过大约70%的数据.

    显然这不是正解,因此我们需要换个思路.

    那就需要考虑题目的特殊性质.我们可以看出,小的字符串长度只有1e3,我们可以利用这个性质.显然最长公共子序列的长度不会超过小的序列的长度,所以我们可以考虑进行如下的DP.

    不妨令S表示长的序列,T表示短的序列,设dp[i][j]表示T的前i位和S的最长公共子序列的最后一位最小是多少,设对了状态就很容易做了,下面给出代码.

    #include<cmath>
    #include<cstdio>
    #include<iostream>
    #include<cstdlib>
    #include<algorithm>
    #include<cstring>
    #include<map>
    #include<queue>
    #include<set>
    #include<vector>
    #include<bitset>
    using namespace std;
    const int maxn=1000005;
    int dp[1005][1005],weizhi[30][maxn],len1,len2,s1[maxn],s2[maxn],cnt[30],ss[maxn];
    char a[maxn];
    inline int read()
    {
        int x=0,f=1;
        char ch=getchar();
        while(ch<'0'||ch>'9')
        {
            if(ch=='-')
                f=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*f;
    }
    inline void write(int a)
    {
        if(a<0)
        {
            char a='-',b='1';
            putchar(a);
            putchar(b);
        }
        else
        {
            if(a>=10)
                write(a/10);
            putchar(a%10+'0');
        }
    }
    
    int main()
    {
        freopen("string.in","r",stdin);
        freopen("string.out","w",stdout);
        memset(dp,0x3f,sizeof(dp));
        scanf("%s",a);
        len1=strlen(a);
        for(int i=1;i<=len1;++i)
            s1[i]=a[i-1]-'a';
        scanf("%s",a);
        len2=strlen(a);
        for(int i=1;i<=len2;++i)
            s2[i]=a[i-1]-'a';
        if(len1<len2)
        {
            memcpy(ss,s1,sizeof(ss));
            memcpy(s1,s2,sizeof(ss));
            memcpy(s2,ss,sizeof(ss));
            swap(len2,len1);        
        }
        for(int i=1;i<=len1;++i)
            weizhi[s1[i]][++cnt[s1[i]]]=i;
        if(weizhi[s2[1]][1])
            dp[1][1]=weizhi[s2[1]][1];
        for(int i=1;i<=len2;++i)
            dp[i][0]=0;
        for(int i=1;i<len2;++i)
            for(int j=0;j<=i;++j)
            {        
                dp[i+1][j]=min(dp[i+1][j],dp[i][j]);
    //            cout<<i<<' '<<j<<' '<<dp[i][j]<<endl;
                if(cnt[s2[i+1]])
                {
                    int pos=upper_bound(weizhi[s2[i+1]]+1,weizhi[s2[i+1]]+cnt[s2[i+1]]+1,dp[i][j])-weizhi[s2[i+1]];
    //                cout<<"NMSL"<<i<<' '<<j<<' '<<dp[i][j]<<' '<<weizhi[s2[i+1]][pos]<<endl;
                    if(pos<=cnt[s2[i+1]])
                        dp[i+1][j+1]=min(dp[i+1][j+1],weizhi[s2[i+1]][pos]);
                }
            }
        for(int i=len2;i>=1;--i)
            if(dp[len2][i]<=1e9)
            {
                write(i);
                return 0;
            }
        write(0);
        return 0;
    }
    View Code

    复杂度为O(m²logn+n)

    不要迫害Eriri啦 ~~~~(>_<)~~~~
  • 相关阅读:
    容器占用空间的小问题
    一个ipv4到ipv6的移植问题
    一个linux内核模块移植到低版本时发生的异常
    一个发包乱序问题记录
    一个docker镜像中的目录删除不了问题
    C/C++(共用体与枚举)
    C/C++(数据结构栈的实现)
    C/C++(结构体)
    C/C++(内存管理)
    C/C++(指针数组)
  • 原文地址:https://www.cnblogs.com/520Enterprise/p/12050187.html
Copyright © 2011-2022 走看看