NOIP2015 子串

来源:互联网 发布:淘宝手机充值卡换现金 编辑:程序博客网 时间:2024/05/16 14:38

NOIP2015子串

题面:点击查看

题解

雏形

  用leni,j表示a串中以a[i]为结尾的后缀,和b串中以b[i]为结尾的后缀匹配的最长长度,这个可以用简单的O(nmk)的预处理求出
  设计状态:fi,j,k表示a串中以a[i]为结尾的后缀,和b串中以b[j]为结尾的后缀匹配,a串中k个子串加起来和b[1]...b[j]相等,状态转移方程:

fi,j,k=l=1leni,jt=1ilft,jl,k1

伪代码

for(k=1 to K)    for(i=1 to N)        for(j=1 to M)            for(l=1 to len[i][j])                for(t=1 to l)                        f[i][j][k]+=f[t][l][k-1]

  上述的时间复杂度在最差情况下是O(NM4)约等于1.6×1012,空间复杂度O(nmk)约等于4×107,完爆

优化

  观察方程

fi,j,k=l=1leni,jt=1ilft,jl,k1

  如果把f数组想象成三维坐标系中的一些坐标为整数的点,那么每一个与k轴垂直的平面的转移都只需要用到前一个平面上的值,所以可以加一个滚动数组,这样就能把空间复杂度降到O(nm)
  继续,由于lt=1表示的是一段连续的区间,就意味着我们可以进行前缀和优化,令
si,j,k=t=1ift,j,k

  那么状态转移方程变为
fi,j,k=l=1leni,jsil,jl,k1

  其中由于s数组是前缀和,所以直接用O(1)的时间进行转移,si,j=si1,j+fi,j。最终时间复杂度O(nmk×)最坏情况下约等于8×109
  按照联赛的数据,这样是可以AC的,但是这样并不完美,如果有一种数据把nmk都开到最大,并且字符串形如aa.....aaaa这样只有一种字母,那就会超时了
  决定性的优化:前缀和的前缀和
  根据上面的方程,仍然发现的循环变量的取值是连续的,所以继续使用前缀和优化,令
  
ssi,j,k=tmin(i,j)sit,jt,k

  这个也可以用递推式在O(1)的时间内算出
  最后的状态转移方程:
  
fi,j,k=ssi1,j1,k1ssmax(0,ileni,j1),max(0,jleni,j),k1

  最后的时间复杂度O(nmk),最坏情况下约等于4×107

刻骨铭心的教训

  这道题我其实只用了一小会就写出正解了,但是由于没看到“输出答案对1,000,000,007取模的结果”竟然查错查了一上午!

代码

//NOIP2015 子串 动态规划 #include <cstdio>#include <algorithm>#define p 1000000007#define ll long long#define maxn 1010#define maxm 210using namespace std;ll f[maxn][maxm][2], s[maxn][maxm][2], ss[maxn][maxm][2],   len[maxn][maxm], N, M, K;char a[maxn]={0}, b[maxm]={1};void init(){    ll i, j, l;    scanf("%lld%lld%lld%s%s",&N,&M,&K,a+1,b+1);    for(i=1;i<=N;i++)        for(j=1;j<=M;j++)            for(l=1;a[i-l+1]==b[j-l+1];len[i][j]=l++);}void dp(){    ll i, j, k;    for(i=1;i<=N;i++)        for(j=1;j<=M;j++)        {            f[i][j][1]=len[i][j]==j;            s[i][j][1]=s[i-1][j][1]+f[i][j][1];            ss[i][j][1]=ss[i-1][j-1][1]+s[i][j][1];        }    for(k=2;k<=K;k++)    {        for(i=1;i<=N;i++)            for(j=1;j<=M;j++)            {                f[i][j][k&1]=(ss[i-1][j-1][~k&1]-ss[max((ll)0,i-len[i][j]-1)][max((ll)0,j-len[i][j]-1)][~k&1])%p;                s[i][j][k&1]=(s[i-1][j][k&1]+f[i][j][k&1])%p;                ss[i][j][k&1]=(ss[i-1][j-1][k&1]+s[i][j][k&1])%p;            }    }}int main(){    ll i, ans;    init();    dp();    for(i=M,ans=0;i<=N;ans=(ans+f[i++][M][K&1])%p);    printf("%lld\n",(ans+p)%p);    return 0;}
0 0