P3228 [HNOI2013] 数列

发布时间 2024-01-07 16:23:14作者: cloud_eve

题意

给定 \(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,要在输入完之后及时取模。