P3228 [HNOI2013] 数列
题意
给定 \(n\)、\(m\)、\(k\) 和 \(p\),要求在 \(n\) 中取 \(k\) 个数,并且相邻的两个数字差小于等于 \(m\),最后对 \(p\) 取模。
思路
如果直接考虑求出这个数列的话,过程相对麻烦,实现起来比较困难,所以不妨换一种思路。
注意题目中有一个非常特殊的数据: \(m\),数列中相邻的两个数差一定小于等于 \(m\),这为解题提供了另一种思路——从差的角度考虑。
那么如果将数列中每两个相邻的数的差表示为一个数组 \(a\),用 \(a_i\) 表示第 \(i\) 天到第 \(i + 1\) 天数列差的变化。为了满足题意,很明显,\(a_i \in [1 , m]\)。
那么对于一种情况,答案就可以表示为 \(n - \sum_{i = 1}^{k-1} a_i\)。由于题目中有叙述 \(m(k - 1) < n\),易得总共应有 \(m^{k - 1}\) 个不同的数列,所以答案可以表示为 \(m^{k - 1} \times (n - \sum_{i = 1}^{k - 1} a_i)\)。
接下来的步骤就是对 \(m^{k - 1} \times (n - \sum_{i = 1}^{k - 1} a_i)\) 进行处理。可以把原式拆开,即 \(m^{k - 1} \times n - m^{k - 1} \times \sum_{i = 1}^{k - 1} a_i\)。对于全排列中的每一个数,出现的次数都是相同的,即 \([1,m]\) 中的每一个数平均出现了 \(m^{k - 1} \times \frac{k - 1}{m} = m^{k - 2} \times (k - 1)\) 次。所以可以把原式变为 \(m^{k - 1} \times n - m^{k - 2} \times (k - 1) \times \sum_{i = 1}^m i\),再用求和公式化简,即可得到 \(ans = m^{k - 1} \times n - m^{k - 2} \times (k - 1) \times \frac{m(m-1)}{2}\)。
代码实现使用快速幂即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll n, m, k, mod, ans;
ll qpow(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b /= 2;
}
return res;
}
int main() {
scanf ("%lld%lld%lld%lld", &n, &k, &m, &mod);
n %= mod;
ans = (qpow(m, k - 1) * n % mod - qpow(m, k - 2) * ((m * (m + 1) / 2ll) % mod) % mod * (k - 1) % mod + mod) % mod;
printf ("%lld", ans);
return 0;
}
注意,由于 \(n\) 的值较大,在运算中可能会爆
long long
,要在输入完之后及时取模。
Blog by cloud_eve is licensed under CC BY-NC-SA 4.0