洛谷P2481 [SDOI2010] 代码拍卖会 题解

一.题面:点这里

二.思路:

看到 \(P\) 的范围,以及整除性,我们自然的想到考虑按模 \(P\) 分类。然后观察这个超长数本身的特性,一般情况下它可以理解为出现一段相同的数后出现一个分界线,然后加上一个很小的数 \(t\) 。所以我灵光一现,就考虑把这个数拆成若干个 \(000...011...1111\) 的形式的数相加,注意此处的写法虽然不规范含有前导零,但是方便理解。那么每一个 \(01\) 分界点也就是我们数里的区间断点。那么经常做模运算相关题目的小朋友们都知道大部分情况下模运算相关具备周期性,事实上这道题也如此,我们如果定义 \(f_i\) 表示长度为 \(i\) 的 \(1111...11\) 的数对 \(P\) 取模的值,显然有递推式:

\[f_i = (10 \times f_{i-1} + 1)\mod P \]

那么根据鸽巢原理,在最多第 \(P + 1\) 次的迭代中会出现重复的值,那么在模 \(P\) 的意义下就出现了循环。

这个结论启发我们定义一个辅助数组 \(g_i\) ,表示模数为 \(i\) 的形如 \(11...11\) 的数有多少。因为有了循环,我们计算这个数列极其高效的,我们对于较大的 \(P\) 我们可以考虑暴力把循环出现之前的部分处理掉,然后用数学原理处理循环内部的值。

我们现在考虑计数答案,一个自然的想法是定义 \(F(k,r)\) 表示选了 \(k\) 个数,模意义下和为 r 的方案数,但是发现转移是极其困难的,原因在于会出现重复并不好递推处理,比如说对于 \(F(k,2r\mod P)\) 包含两个状态 \(\{f_a,f_b\},\{f_a,f_a\}\),然后下一次转移到 \(F(k+1,3r\mod P)\) 时往第一个状态中加入 \(f_a\) 和往第二个状态中加入 \(f_b\) 的情况实际是一样的,因为我们的长度为 \(n\) 的数时单调递增的,所以所有数的排列方式是由单调性唯一确定的。但是我们发现刚才的过程实际上是产生了排列,而我们从最终结果来看是只需要一个组合的贡献。

所以我们去考虑对状态的定义增加限制,我们定义 \(F(k,r,t)\) 表示模数类为 \([0,t-1]\) 的数已经被考虑完,考虑选取第 \(t\) 类数且最终选了 \(k\) 个数,和的模数为 \(r\) 的方案数,这样定义的好处在于我们使得每一类的选取是符合我们预期限制的,因为不同类之间不会存在冲突。可以得到最后的转移方程为:

\[F(k,(r+(l\times t))\mod P,t+1)=\dbinom{g_t + l - 1}{l}\times\sum_l F(k-l,r,t) \]

这里的组合数是怎么来的呢,考虑集合 \(g_t\) 中的元素,他们虽然在模意义下是无区别的,但是对于原数是不同的方案,这个问题等价于集合中有 \(g_t\) 个元素,每种元素有无穷多种,现要选出 \(l\) 个元素的组合,求有多少种方案。这个问题看似很难解决,但是我们可以换一种思考方式,原问题等价于,我们有 \(l\) 个小球,小球本身没有区别,现有 \(g_t\) 个盒子,盒子两两不同,将小球放入盒子后,小球会被赋予种类,盒子可以空,求有多少种组合方案。这是一个经典的小球盒子问题,无论用隔板法或者多重集合的相关知识都可以证明答案就是 \(\dbinom{g_t + l - 1}{l}\) 。

此外我们需要注意的是,原本的数中一定可以拆出来一种全是 \(1\) 没有 \(0\) 的方案,我们需要求出这个值赋予其初始值在dp的初始值中。

三.Code:

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#define int long long
inline int read() {
	int x=0,f=1;char ch=getchar();
	while(ch > '9' || ch < '0'){if(ch == '-'){f = -1;}ch = getchar();}
	while(ch >= '0'&&ch <= '9'){x = x * 10 + ch - 48; ch = getchar();}
	return x * f;
}

const int MP = 500 + 10, MOD = 999911659;
int f[MP][11][MP], g[MP], inv[11], C[505][11], n, P, sum, pos[MP];

signed main() {
	n = read(), P = read();
	inv[0] = inv[1] = 1; 
	
	if (n <= P) {
		for (int i = 1; i <= n; ++i) {
			sum = (sum * 10 + 1) % P;
			++g[sum];
		}
	}
	else {
		int tot = 1 % P, len, loc; 
		for (int i = 1; i <= P + 1; ++i) {
			if (pos[tot]) {
				loc = pos[tot], len = i - loc;
				break;
			}
			pos[tot] = i, ++g[tot], tot = (tot * 10 + 1) % P;
		}
		
		for (int i = 0; i < P; ++i) {
			if (pos[i] && pos[i] >= loc) {
				g[i] += ((n - pos[i]) / len) % MOD;
				if ((pos[i] - loc + 1) % len == (n - loc + 1) % len) sum = i;
			}
		}
	}
	
	for (int i = 2; i <= 9; ++i) inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
	
	for (int i = 0; i < P; ++i) {
		C[i][0] = 1;
		if (!g[i]) continue;
		for (int j = 1; j < 9; ++j, g[i] = (g[i] + 1) % MOD)
			C[i][j] = C[i][j - 1] * g[i] % MOD * inv[j] % MOD;
	}
	
	f[0][0][sum] = 1;
	for (int i = 0; i < P; ++i) {
		for (int j = 0; j < P; ++j) {
			for (int k = 0; k < 9; ++k) {
				for (int t = 0; t < 9 - k; ++t) {
					(f[i + 1][t + k][(j + t * i) % P] += f[i][k][j] * C[i][t] % MOD) % MOD;
				}
			} 
		}
	}
	
	int ans = 0;
	for (int i = 0; i < 9; ++i) ans = (ans + f[P][i][0]) % MOD;
	
	printf("%lld", ans);
	return 0;
}
相关推荐
Code9200710 个月前
The 2024 ICPC Kunming Invitational Contest K. Permutation(交互 期望)
交互·概率·期望·思维题
Code9200710 个月前
Codeforces Round 975 (Div. 1) D. Max Plus Min Plus Size(思维题 并查集/动态dp 线段树维护状态合并)
并查集·思维题·动态dp
kanade1610 个月前
Codeforces Round 973 (Div. 2) D
思维题
Code920071 年前
Codeforces Round 951 (Div. 2) F. Kostyanych‘s Theorem(思维题 交互好题)
交互·思维题
Code920071 年前
Codeforces Round 948 (Div. 2) E. Tensor(思维题-交互)
思维题·乱搞ac