[HNOI2013]数列(差分)

本文解析了一道关于数列的洛谷竞赛题目,通过差分数组技巧解决每天股价增长限制的问题。关键在于理解每天股价的最大增值、差分数组的贡献以及不同差分序列的计数。最终给出了一个利用等差数列求和公式计算答案的简洁解决方案,并省去了使用逆元的复杂步骤。
摘要由CSDN通过智能技术生成

[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(k1)<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] ni=1k1s[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,ni=1k1s[i]] 的时候,都有 a [ k ] ≤ n a[k]\le n a[k]n

而本质不同的差分数列的个数又为 m k − 1 m^{k-1} mk1,前面说过任何一天的增值都有可能取到 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=1mk1(ni=1k1sj[i])=nmk1j=1mk1i=1k1sj[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) mk1(k1) 个数,并且在 [ 1 , m ] [1,m] [1,m] 是均匀分布。

所以 [ 1 , m ] [1,m] [1,m] 中的每个数出现次数都是一样的,即 m k − 2 ( k − 1 ) m^{k-2}(k-1) mk2(k1)。在乘上数值即可。

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=nmk1j=1mk1i=1k1sj[i]=nmk1i=1mimk2(k1)

后面的式子就是个等差数列求和而已。

最后化简答案: 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=nmk12(m+1)mmk2(k1)

有的题解觉得这个除法有点坑,有的用了 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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值