[HNOI2013]数列
problem
solution
假设每天的股价为 a [ i ] a[i] a[i]。则需满足 ∀ i < k a [ i + 1 ] − a [ i ] ≤ m \forall_{i<k}a[i+1]-a[i]\le m ∀i<ka[i+1]−a[i]≤m。又有参数满足 m ( k − 1 ) < n m(k-1)<n m(k−1)<n。
也就是说每天的股价都可以取到上限,即 ∀ i < k a [ i + 1 ] − a [ i ] = m \forall_{i<k}a[i+1]-a[i]=m ∀i<ka[i+1]−a[i]=m。
当然都要保证 a [ k ] ≤ n a[k]\le n a[k]≤n。
这种每天都有增长求方案数的题目,容易联想到差分。因为当差分数组确定时,就只与第一个元素的值有关了。
用差分刻画数组 是很经典的套路。
令 ∀ i < k s [ i ] = a [ i + 1 ] − a [ i ] \forall_{i<k}s[i]=a[i+1]-a[i] ∀i<ks[i]=a[i+1]−a[i]。
一个差分数组贡献都是 1 1 1。但是有若干种股价方案的差分数组是同一个,就可以合并计算。
这里可以采用对第一天股价的起始值刻画出差分的增值一样但本质不同的股价方案。
显然,方案数为 n − ∑ i = 1 k − 1 s [ i ] n-\sum_{i=1}^{k-1}s[i] n−∑i=1k−1s[i]。因为在 a [ 1 ] ∈ [ 1 , n − ∑ i = 1 k − 1 s [ i ] ] a[1]\in[1,n-\sum_{i=1}^{k-1}s[i]] a[1]∈[1,n−∑i=1k−1s[i]] 的时候,都有 a [ k ] ≤ n a[k]\le n a[k]≤n。
而本质不同的差分数列的个数又为 m k − 1 m^{k-1} mk−1,前面说过任何一天的增值都有可能取到 m m m。
综上可以得出答案的式子: ∑ j = 1 m k − 1 ( n − ∑ i = 1 k − 1 s j [ i ] ) = n m k − 1 − ∑ j = 1 m k − 1 ∑ i = 1 k − 1 s j [ i ] \sum_{j=1}^{m^{k-1}}(n-\sum_{i=1}^{k-1}s_j[i])=nm^{k-1}-\sum_{j=1}^{m^{k-1}}\sum_{i=1}^{k-1}s_j[i] ∑j=1mk−1(n−∑i=1k−1sj[i])=nmk−1−∑j=1mk−1∑i=1k−1sj[i]。
显然 s s s 是将所有可能的合法差分数列都计算到了。并且有 s j [ i ] ∈ [ 1 , m ] s_j[i]\in[1,m] sj[i]∈[1,m]。
先不管差分数值,那么后面一共就会有 m k − 1 ( k − 1 ) m^{k-1}(k-1) mk−1(k−1) 个数,并且在 [ 1 , m ] [1,m] [1,m] 是均匀分布。
所以 [ 1 , m ] [1,m] [1,m] 中的每个数出现次数都是一样的,即 m k − 2 ( k − 1 ) m^{k-2}(k-1) mk−2(k−1)。在乘上数值即可。
a n s = n m k − 1 − ∑ j = 1 m k − 1 ∑ i = 1 k − 1 s j [ i ] = n m k − 1 − ∑ i = 1 m i ∗ m k − 2 ( k − 1 ) ans=nm^{k-1}-\sum_{j=1}^{m^{k-1}}\sum_{i=1}^{k-1}s_j[i]=nm^{k-1}-\sum_{i=1}^mi*m^{k-2}(k-1) ans=nmk−1−∑j=1mk−1∑i=1k−1sj[i]=nmk−1−∑i=1mi∗mk−2(k−1)。
后面的式子就是个等差数列求和而已。
最后化简答案: a n s = n m k − 1 − ( m + 1 ) m 2 m k − 2 ( k − 1 ) ans=nm^{k-1}-\frac{(m+1)m}{2}m^{k-2}(k-1) ans=nmk−1−2(m+1)mmk−2(k−1)。
有的题解觉得这个除法有点坑,有的用了 exgcd
求解逆元。
根本没有这种必要啊?!
逆元是因为不能整除所以才在 p p p 域内去找有相同效果的数。
但是明显 m ( m + 1 ) m(m+1) m(m+1) 一定有一个是偶数,一定能被 2 2 2 整除。
code
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, m, k, p;
int qkpow( int x, int y ) {
int ans = 1;
while( y ) {
if( y & 1 ) ans = ans * x % p;
x = x * x % p;
y >>= 1;
}
return ans;
}
signed main() {
scanf( "%lld %lld %lld %lld", &n, &k, &m, &p );
printf( "%lld\n", ( n % p * qkpow(m, k - 1) % p - qkpow(m, k - 2) * (k - 1) % p * (m * (m + 1) / 2 % p) % p + p ) % p );
return 0;
}