51nod【1196】字符串的数量
超级神题!
有n种字符,若此种字符的编号( \(1\) ~ \(n\)),\(i*2>n\),则他后面可接任意字符。若不是,则他后面接的字符编号至少要是他的两倍。
问长度为m的字符串的个数。
这道题我只想出了 \(O(n^2)\) 的做法,于是叕只能求助题解。
题解的做法和周六第二题有点像,但我并没有分析出最长链的长度小于 \(log_{2}n\) 这个性质。知道这个性质之后,就可以做了。
设 \(f[i]\) 表示长度为 \(i\) 的合法字符串的个数。\(g[i]\) 表示长度为 \(i\) 的合法链的个数。
转移就是
\[f[i]=\sum_{j=1}^{maxlen}g[j]*f[i-j]
\]
但 \(g[i]\) 并不能直接转移,所以要设个辅助状态。
设 \(p[i][j]\) 表示以 \(j\) 结尾,长度为 \(i\) 的链的个数,\(g[i]\) 就是 \(p[i]\) 里合法链的和。
\[p[i][j]=\sum_{k=1}^{j/2}p[i-1][k]
\]
这个可以用前缀和优化,也可以将式子化简为从 \(p[i][j-1]\) 转移过来。
\[p[i][j]=p[i][j-1]+p[i-1][j/2]*(j\ mod\ 2==0)
\]
总复杂度 \(O(m*log_{2}n)\)。
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define RG register
inline int gi()
{
RG int ret; RG bool flag; RG char ch;
ret=0, flag=true, ch=getchar();
while (ch < '0' || ch > '9')
ch == '-' ? flag=false : 0, ch=getchar();
while (ch >= '0' && ch <= '9')
ret=(ret<<3)+(ret<<1)+ch-'0', ch=getchar();
return flag ? ret : -ret;
}
const db pi = acos(-1.0);
const int N = 1e6+5, mod = 1e9+7;
int f[N],g[N],p[22][N];
int main()
{
RG int n,m,len,i,j;
n=gi(), m=gi();
len=log2(n)+2;
p[1][1]=1, f[0]=1;
for (i=1; i<=len; ++i)
{
for (j=1; j<=n; ++j)
{
(p[i][j]+=p[i][j-1])%=mod;
if (j<<1 <= n)
(p[i+1][j<<1]+=p[i][j])%=mod;
else
(g[i]+=p[i][j])%=mod;
}
}
for (i=1; i<=m; ++i)
for (j=1; j<=len && j<=i; ++j)
(f[i]+=((ll)f[i-j]*g[j])%mod)%=mod;
printf("%d\n",f[m]);
return 0;
}