Codeforces 1142D(dp)

题目传送

先给出设计dp的结论:

dp[i][j]:以第i个位置、以rankj的数拓展出去的方案数。意会一下,我实在想不好语言……

其中所谓rankj=真·rank%11

找到拓展的规律,转移也就顺理成章了。不妨先看1~9拓展到的二位数里有哪些是合法拓展:

\[Rank1\%11=1:所以数字1\rightarrow10 \]

\[Rank2\%11=2:所以数字2\rightarrow20\ 21 \]

\[…… \]

\[Rank9\%11=9:所以数字9\rightarrow90\ 91\ 92\ 93\ 94\ 95\ 96\ 97\ 98 \]

再拓展到三位数找找规律:

\[Rank10\%11=10:所以数字10\rightarrow100\ 101......109 \]

\[Rank11\%11=0:所以数字20\rightarrow无法拓展 \]

\[Rank12\%11=1:所以数字21\rightarrow210 \]

\[Rank13\%11=2:所以数字30\rightarrow300\ 301 \]

可以看出能够拓展的个数也是按照%11的规则循环的,所以我们要在rank之间做转移(以下说rank都是%11以后的rank,不%11的话状态太多)

规律:
1.当前rank转移过去的数字,个位数字一定小于rank

2.如果我们每11个合法数分为一组,则这11个数将拓展出新的55个数(注意这是会%11 = 0的),也就意味着$$0\ 1\ 2\ 3\ 4\ 5\ 6\ 7\ 8\ 9\ 10\ $$$$20\ 21\ 30\ 31\ 32\ 40\ 41\ 42\ 43\ 50\ 51$$$$……$$每11个数会拓展出55个新数(反正0也不贡献,可加)。
现在举例:$$51排名j为10;$$$$51拓展出的\rightarrow510的排名j'将为10$$怎么算的呢?更普遍化地来说,当前数字排名为j时,假如它向后拓展一位,然后个位数字为d,则新数字排名为$$(\frac{j(j-1)}{2}+(d+1)+(10-1))\ %\ 11$$这样理解,比如51要拓展到512,$$\frac{109}{2}是20~50拓展出的三位数个数$$$$d+1=2+1是510~512$$$$10-1是1~9的rank加上去$$
对于第一条,发现0~10拓展出55个数(我们一开始就列了),所以0~10拓展出的数目%11后为0并不影响rank。所以只计算20~50的即可。其他也是同理,因为我们是11个一组的;
第二条不解释;
对于第三条,我们只加了0~10拓展的数为开始的,却没加1~10(这时就别算0了),然后为啥-1呢?因为0~10拓展的数里本来就有个10了,10算了两遍。也就相当于把1~9加上。

懂了以上式子以后就可以直接看代码了:

#include <cstdio>
#include <cstring>

const int maxn = 1e5 + 5;
char S[maxn];
__int64_t ans, dp[maxn][15];//以第i个位置、以rankj的数拓展出去的方案数

int main() {
	scanf("%s", S + 1);
	int len = strlen(S + 1);

	for (int i = len; i; --i) {
		int d = S[i] - '0';

		for (int j = 0; j <= 10; ++j) {
			dp[i][j] = 1;//自己独立成一个方案
			if (i < len) {
				int t = S[i + 1] - '0';
				if (j > t) {//与后面联合,当前rank只会转移到更小的数字
					dp[i][j] += dp[i + 1][(j * (j - 1) / 2 + t + 10) % 11];//事实上只有两位数的转移也可以直接打表
				}
			}	
		}

		if (d)	ans += dp[i][d];//题目限制从1~9为起点
	}

	printf("%lld\n", ans);
	return 0;
}
posted @ 2019-04-07 13:39  AlphaWA  阅读(538)  评论(2编辑  收藏  举报