1 条题解
-
0
自动搬运
来自洛谷,原作者为

Imakf
**搬运于
2025-08-24 21:38:11,当前版本为作者最后更新于2020-01-13 22:06:50,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题意转化
本题的转化非常妙妙!
对于任何一个猪猪举牌的方案,都可以看做 个以内的若干 “ 的后缀”相加而成!
感性理解如下图:

于是我们把一个方案拆成了若干的形如 的数字相加!
题目中让我们求 意义下为 的方案数。
想想怎么做?
我们可以把一个数分割成若干个 的和。
所以我们可以计算出 个 “ 的后缀”在 意义下 的值。
因为可以选至多 种后缀,所以就可以跑 暴力!
然而这一个点都过不去啊其实我们可以直接把 意义下有相同数值的后缀看成同一类
比如 和 在 意义下是同一类。
不妨记 为 意义下[余数是 的“ 的后缀”]的数量。
(最后再讲 的求法,想看可以直接跳)
我们就可以不用暴力
而是做一个背包的转移就好了!
背包
设 表示当前考虑到余数为 的 “ 的后缀”,此前已经放上了 个“ 的后缀”,此时构成的数字的 的余数是 的方案数。
枚举“ 的后缀” 意义下的余数 ,记为 。
枚举当前已经铺了几个“ 的后缀”。,记为 。
为什么是 ,因为最左边的猪是不能出价为 的,**所以最初要强制铺一层全 **。
枚举当前这种 “ 的后缀”铺了几个。,记为 。
枚举转移过来的状态 意义下的余数。,记为 。
则转移方程是:
$$dp[p][s+j][(d+s*i+p)\%p]+=\sum\limits \dbinom{g[i]+s-1}{s}dp[p-1][j][d] $$这个 是什么呢?
就是在 中选 个同种 “ 的后缀”有多少种不同的方法!
证明即隔板法。
复杂度即
至于怎么计算这个组合数,直接按定义计算就好。
循环节的计算
那么怎么计算 数组呢?
普及组知识?定义 表示 个 连起来形成的数, 为它在 意义下的值。
如
则显然有
显然这柿子在 次迭代内必然会出现循环。
可以把 分成三段:
-
未进入循环段
-
循环段
-
不完整循环段
暴力统计 未进入和不完整 的,用循环节搞定循环的就好了。
代码如下:
#include <cstring> #include <iostream> using namespace std; #define mod (999911659LL) #define MX (500 + 5) #define LL long long LL qpow(LL x ,LL y ,LL P){ if(!y) return 1; if(y == 1) return x; LL t = qpow(x ,y >> 1 ,P); if(y & 1) return t * t % P * x % P; return t * t % P; } LL g[MX] ,n ,p ,cycLen ,cycstNum; int cycle[MX * MX] ,first[MX]; LL dp[MX][11][MX]; LL C(LL n ,LL m){ if(m < 0) return 0; LL fz = 1 ,fm = 1; for(int i = 0 ; i < m; ++i){ (fz *= n - i) %= mod; (fm *= m - i) %= mod; } return fz * qpow(fm ,mod - 2 ,mod) % mod; } int main(){ // cout << C(10 ,7); memset(first ,-1 ,sizeof first); cin >> n >> p; cycle[0] = 1 % p; first[1 % p] = 0; LL addition = 0; // first 是该数第一次出现的位置 // cycle 是按顺序出现的数 %p for(int i = 1 ; ; ++i){ cycle[i] = (cycle[i - 1] * 10 + 1) % p; if(~first[cycle[i]]){ for(int j = 0 ; j < i && j < n; ++j) g[cycle[j]]++ ,addition = cycle[j]; n -= i; cycstNum = cycle[i]; cycLen = i - first[cycle[i]]; break; } first[cycle[i]] = i; } n = max(n ,0LL); LL times = n / cycLen; for(int i = 0 ; i < cycLen ; ++i) g[cycle[i + first[cycstNum]]] += times; LL nn = n % cycLen; for(int i = 0 ; i < nn ; ++i) g[cycle[i + first[cycstNum]]]++ ,addition = cycle[i + first[cycstNum]]; for(int i = 0 ; i < p ; ++i) g[i] %= mod; // 因为我很懒,按照上文博客 // 在计算 dp[0][][] 的时候会调用 dp[-1][][] 导致 RE // 所以我是倒着来的,把 dp[0] 变成 dp[p] ,dp[1] 变成 dp[p - 1]... dp[p + 1][0][addition] = 1; // 此处 addition 是强制要求选择一次全 111111... 造成的代价 for(int i = 0 ; i < p ; ++i){ // 枚举使用的 0000...1111 %p 意义的值 for(int j = 0 ; j < 9 ; ++j){ // 枚举已经使用次数 for(int s = 0 ; s + j < 9 ; ++s){ // 枚举当前使用次数 LL multi = C(g[i] + s - 1 ,s); for(int d = 0 ; d < p ; ++d){ // 枚举已经的余数 // if(dp[p - i + 1][j][d]){printf("dp[%lld][%d][%lld] += multi(%lld) * dp[%lld][%d][%d](%lld);\n" ,p - i ,s + j ,(d + s * i) % p ,multi ,p - i + 1 ,j ,d ,dp[p - i + 1][j][d]);} (dp[p - i][s + j][(d + s * i) % p] += multi * dp[p - i + 1][j][d]) %= mod; } } } } LL Ans = 0; for(int i = 0 ; i < 9 ; ++i){ (Ans += dp[1][i][0]) %= mod; } cout << Ans << endl; return 0; } ``` -
- 1
信息
- ID
- 1519
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者