洛谷 P3228 [HNOI2013] 数列
这道题最难的其实是想到把【构造一个上升序列】转化为【构造一个差分序列】(当然我是想不到的,所以看了题解的一部分)。了解此思路下的我经过一顿推公式之后依旧只推出了 30pts 的暴力公式和代码,然后看了题解豁然开朗,所以决定写一篇题解来说说暴力和正解的思路。
题目传送门
前言
这道题最难的其实是想到把 【构造一个上升序列】 转化为 【构造一个差分序列】(当然我是想不到的,所以看了题解的一部分)。
了解此思路下的我经过一顿推公式之后 依旧只推出了 30pts 的暴力公式和代码,然后看了题解 豁然开朗,所以决定写一篇题解来说说暴力和正解的思路。
整体思路
正如前言所说,我们把每一天股票增长的差分数组
d
i
d_i
di 设出来,
d
i
d_i
di 的取值范围是
[
1
,
m
]
[1, m]
[1,m]。
假设我们已经构造出了
[
1
,
k
−
1
]
[1, k - 1]
[1,k−1] 天的差分数组
d
1...
k
−
1
d_{1...k-1}
d1...k−1,那么最后一天就有
n
−
∑
i
=
1
k
−
1
d
i
n - \sum_{i = 1}^{k - 1} d_i
n−∑i=1k−1di 种可能。
那这个式子有没有可能小于等于
0
?
0 \ ?
0 ?
答案是否定的,因为题目中规定了数据范围
m
×
(
k
−
1
)
<
n
m \times (k - 1) < n
m×(k−1)<n,只要
d
i
d_i
di 取值合法,那这个式子就大于
0
0
0 。
由于对于我们所构造的
[
1
,
k
−
1
]
[1, k - 1]
[1,k−1] 的差分数组一共有
m
k
−
1
m^{k - 1}
mk−1 个,所以总的方案数就是:
∑
i
=
1
m
k
−
1
n
−
s
u
m
d
i
=
n
×
m
k
−
1
−
∑
i
=
1
m
k
−
1
s
u
m
d
i
\sum_{i = 1}^{m^{k - 1}} n - sumd_{i} = n \times m^{k - 1} - \sum_{i = 1}^{m^{k - 1}}sumd_i
i=1∑mk−1n−sumdi=n×mk−1−i=1∑mk−1sumdi
其中,
s
u
m
d
i
sumd_i
sumdi 表示构造的第
i
i
i 个差分序列的
d
d
d 值之和。
现在看来不好处理的就是后面这个和式,下来我将讲述暴力和正解两种思路。
暴力
我们尝试使用常常用来求和式的贡献法。
对于一个数
x
∈
[
1
,
m
]
x \in [1, m]
x∈[1,m],我们考虑它的出现次数。
假设在一些序列中
x
x
x 出现了
i
i
i 次,那这样的序列有多少个呢 ?
答案是
C
k
−
1
i
×
(
m
−
1
)
k
−
i
−
1
C_{k - 1}^{i} \ \times (m - 1)^{k - i - 1}
Ck−1i ×(m−1)k−i−1 次:
C
k
−
1
i
C_{k - 1}^{i}
Ck−1i 表示从
k
−
1
k - 1
k−1 个位置中选出
i
i
i 个放
x
x
x,剩下的位置共
k
−
i
−
1
k - i - 1
k−i−1 个。由于
i
i
i 个
x
x
x 已经放完,所以剩下的位置每个只有
m
−
1
m - 1
m−1 种选择(就是还可以放
[
1
,
m
]
[1, m]
[1,m] 中除去
x
x
x 后剩下的数),所以一共有
C
k
−
1
i
×
(
m
−
1
)
k
−
i
−
1
C_{k - 1}^{i} \ \times (m - 1)^{k - i - 1}
Ck−1i ×(m−1)k−i−1 种选择。
那么出现
i
i
i 个
x
x
x 的所有序列的贡献就是
x
×
(
∑
i
=
1
k
−
1
i
×
C
k
−
1
i
×
(
m
−
1
)
k
−
i
−
1
)
x \times (\sum_{i = 1} ^ {k - 1} i \times C_{k - 1}^{i} \times (m - 1) ^ {k - i - 1})
x×(∑i=1k−1i×Ck−1i×(m−1)k−i−1)
那么总答案就是:
n
×
m
k
−
1
−
∑
i
=
1
m
k
−
1
s
u
m
d
i
=
n
×
m
k
−
1
−
(
∑
x
=
1
m
x
×
(
∑
i
=
1
k
−
1
i
×
C
k
−
1
i
×
(
m
−
1
)
k
−
i
−
1
)
)
=
n
×
m
k
−
1
−
(
m
×
(
m
+
1
)
2
×
(
∑
i
=
1
k
−
1
i
×
C
k
−
1
i
×
(
m
−
1
)
k
−
i
−
1
)
)
n \times m^{k - 1} - \sum_{i = 1}^{m^{k - 1}} sumd_i= n \times m^{k - 1} - (\sum_{x = 1}^{m} x \times (\sum_{i = 1} ^ {k - 1} i \times C_{k - 1}^{i} \times (m - 1) ^ {k - i - 1})) \\ = n \times m^{k - 1} - (\frac{m \times (m + 1)}{2} \times (\sum_{i = 1} ^ {k - 1} i \times C_{k - 1}^{i} \times (m - 1) ^ {k - i - 1}))
n×mk−1−i=1∑mk−1sumdi=n×mk−1−(x=1∑mx×(i=1∑k−1i×Ck−1i×(m−1)k−i−1))=n×mk−1−(2m×(m+1)×(i=1∑k−1i×Ck−1i×(m−1)k−i−1))
时间复杂度
O ( n × l o g ( p ) ) O(n \times log(p)) O(n×log(p)),期望得分 30 p t s 30\ pts 30 pts 。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e4 + 7;
ll n, m, p, k;
ll fac[maxn], inv[maxn];
ll qpow(ll x, ll y) {
ll res = 1;
for (; y; y >>= 1, x = x * x % p)
if (y & 1) res = res * x % p;
return res;
}
ll C(ll x, ll y) {return fac[x] * inv[x - y] % p * inv[y] % p;}
int main() {
fac[0] = inv[0] = 1;
scanf("%lld%lld%lld%lld", &n, &k, &m, &p);
for (ll i = 1; i <= n; ++i) {
fac[i] = fac[i - 1] * i % p;
inv[i] = qpow(fac[i], p - 2);
// 从数据来看, 所有 p 应该都是质数, 否则这个逆元就是错的(滑稽
}
ll tmp = 0;
for (ll i = 1; i <= k - 1; ++i)
tmp = (tmp + i * C(k - 1, i) % p * qpow(m - 1, k - 1 - i) % p) % p;
tmp = (tmp * m * (m + 1) / 2) % p;
printf("%lld\n", (n * qpow(m, k - 1) % p - tmp + p) % p);
return 0;
}
正解
其实这题求和式的值并不需要贡献法。
不考虑重复,构造的
m
k
−
1
m ^ {k - 1}
mk−1 个序列中共有
m
k
−
1
×
(
k
−
1
)
m ^ {k - 1} \times (k - 1)
mk−1×(k−1) 个数。
由每个数的对称性可知:
[
1
,
m
]
[1, m]
[1,m] 中的每个数的出现次数相等。所以一个数
x
x
x 的出现次数为
m
k
−
1
×
(
k
−
1
)
m
=
m
k
−
2
×
(
k
−
1
)
\frac{m ^ {k - 1} \times (k - 1)}{m} = m ^ {k - 2} \times (k - 1)
mmk−1×(k−1)=mk−2×(k−1)。
那么正解公式就可以呼之欲出了:
n
×
m
k
−
1
−
∑
i
=
1
m
k
−
1
s
u
m
d
i
=
n
×
m
k
−
1
−
m
×
(
m
+
1
)
2
×
m
k
−
2
×
(
k
−
1
)
n \times m^{k - 1} - \sum_{i = 1}^{m^{k - 1}} sumd_i = n \times m^{k - 1} - \frac{m \times (m + 1)}{2} \times m ^ {k - 2} \times (k - 1)
n×mk−1−i=1∑mk−1sumdi=n×mk−1−2m×(m+1)×mk−2×(k−1)
代码实现就是快速幂了。
感觉正解公式的技术含量还不如暴力公式的高
时间复杂度
O ( l o g ( k ) ) O(log(k)) O(log(k)),期望得分 100 p t s 100 \ pts 100 pts 。
代码
#include <bits/stdc++.h>
namespace FIO {
template<typename T> T rd() {
T x = 0, f = 1; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + (c ^ 48); c = getchar();}
return x * f;
}
template<typename T> void wrt(T x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) wrt(x / 10);
putchar(x % 10 + 48);
}
};
using namespace std;
using namespace FIO;
typedef __int128 ll;
const int maxn = 1e6 + 7;
ll n, m, mod, k, ans;
ll qpow(ll x, ll y) {
ll res = 1;
for (; y; y >>= 1, x = x * x % mod)
if (y & 1) res = res * x % mod;
return res;
}
int main() {
n = rd<ll>(), k = rd<ll>(), m = rd<ll>(), mod = rd<ll>();
ans = ((n * qpow(m, k - 1) -
m * (m + 1) / 2 * qpow(m, k - 2) * (k - 1)) % mod + mod) % mod;
wrt(ans);
return 0;
}
更多推荐
所有评论(0)