Codeforces 1142D(dp)

题目传送

先给出设计dp的结论:

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

其中所谓rankj=真·rank%11

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

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

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

[…… ]

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

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

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

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

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

[Rank13\%11=2:所以数字30 ightarrow300 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拓展出的 ightarrow510的排名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
", ans);
	return 0;
}
原文地址:https://www.cnblogs.com/AlphaWA/p/10665100.html