NOIP2015 子串

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 
#include 
#define p 1000000007
#define ll long long
#define maxn 1010
#define maxm 210
using 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;
}

你可能感兴趣的:(#,一般动态规划,字符串)