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;
}
posted @ 2017-09-18 20:20  tbhkoymiads  阅读(127)  评论(0编辑  收藏  举报