水题欢乐赛-套路

序言

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

恩恩真是太对了

题目描述

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 
#include 
#include <string.h>
#include 
#include <string>
#include 
#include 
#include 
#include 
 
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\n",lcs(a,b));
    }
}
View Code

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

 

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

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

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

#include
#include
#include
#include
#include
#include
#include
#include
#include<set>
#include
#include
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;ii)
        for(int j=0;j<=i;++j)
        {        
            dp[i+1][j]=min(dp[i+1][j],dp[i][j]);
//            cout<
            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"<
                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)

你可能感兴趣的:(水题欢乐赛-套路)