NOIP2015子串
题面:点击查看
题解
雏形
用
leni,j
表示
a
串中以
设计状态:
fi,j,k
表示
a
串中以
fi,j,k=∑l=1leni,j∑t=1i−lft,j−l,k−1
伪代码
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,j∑t=1i−lft,j−l,k−1
如果把f数组想象成三维坐标系中的一些坐标为整数的点,那么每一个与k轴垂直的平面的转移都只需要用到前一个平面上的值,所以可以加一个滚动数组,这样就能把空间复杂度降到 O(nm)
继续,由于 ∑lt=1 表示的是一段连续的区间,就意味着我们可以进行前缀和优化,令
si,j,k=∑t=1ift,j,k
那么状态转移方程变为
fi,j,k=∑l=1leni,jsi−l,j−l,k−1
其中由于s数组是前缀和,所以直接用 O(1) 的时间进行转移, si,j=si−1,j+fi,j 。最终时间复杂度 O(nmk×平均匹配长度) 最坏情况下约等于 8×109
按照联赛的数据,这样是可以AC的,但是这样并不完美,如果有一种数据把nmk都开到最大,并且字符串形如 aa.....aaaa 这样只有一种字母,那就会超时了
决定性的优化:前缀和的前缀和
根据上面的方程,仍然发现 ∑ 的循环变量的取值是连续的,所以继续使用前缀和优化,令
ssi,j,k=∑tmin(i,j)si−t,j−t,k
这个也可以用递推式在 O(1) 的时间内算出
最后的状态转移方程:
fi,j,k=ssi−1,j−1,k−1−ssmax(0,i−leni,j−1),max(0,j−leni,j),k−1
最后的时间复杂度 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 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;
}