「APIO2016」划艇 题解

「APIO2016」划艇 题解

混氏新子 蒟蒻

[[dp]] [[树状数组]] [[数论]]

题目概述

题目链接-洛谷

题目链接-loj

这是今天练习赛上想到了一个的方法,结果只有 36 pts,悲催。

后来发现里面有一个式子就是一个组合数,可以递推求解,改了一行代码,AC。(内心嫉妒生草)

解题思路

大多数题解都是选择 dp 区间,然后看哪些在一个区间中,我的方法类似,可以说非常像,有一些步骤是类似的,但是却又有很大的不同。

我的思路是这样的,枚举当前学校,然后选择数量,记这样的情况数为,会发现

接下来我就和大多数题解走上了不一样的道路。我们再记

,其中,非常神奇的发现

(废话)。我们考虑如何更新呢?我们会发现,接着我们把两个数组合并为一个,两维变成一维。怎么变呢?我们发现,对于一个学校的操作是在一个区间内的,对于这个区间内每一个(省略掉了第一维,用树状数组维护),相当于将其加上了它之前所有数的和。此时我们只需要从枚举然后每次加上其位置之前的所有数和就行。而答案就是最后中所有数的和减去,因为,必须有船。

但是这个区间依然是级别的,怎么办呢?我们考虑到离散化,我们记录下每一个小的区间,对于第个学校,我们依然倒着枚举其覆盖的每一个小区间。显然对于这个区间,我们要加上这个区间之前所有段的和,而难点在于维护区间内的情况。

我们手算一下发现,当最开始这个段中都为时,假设段长为对于这个段来说每一个都加上,那么和就是,这时如果又要对这个段每个都加上呢?我们会发现,之前的相当于会求一个前缀和,对于区间内部相当于会从相当于求了个前缀和,然后再加上,并且对于每次加进来的数,我们会发现它们是相互独立的,因此我们将每次加进区间的数存进一个 vector ,然后每次又有新的数加进来时分别计算每个数新增的贡献。手算几遍我们会发现,每次会新增(包括第一次)

用手暴力算几次前缀和找个规律就能发现,我们发现对于一个每次新增的系数其实就是,这里代表这个是第几个加进来的(从开始,最新的会发现少一次,加上就行,这就是为啥上面式子第一个我要拆开的原因)。

但是当时我考试的时候直接去算的这个式子,我十分的不理解。加上我上面的那个式子,其实不用看出来是组合数都能看出每次新增的乘积了吧,递推就行,所以我是傻逼

来看代码,不长。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <bits/stdc++.h>
#define N 510
#define mod 1000000007ll
using namespace std;
using ll = long long;
int n;
ll a[N], b[N];
ll bian[N << 1];
int cnt;
ll tr[N << 1];
ll fact[N], inv[N];
deque <ll> jia[N << 1];
ll qp(ll x, ll y) {
ll ans = 1;
while (y) {
if (y & 1) ans = ans * x % mod;
y >>= 1;
x = x * x % mod;
}
return ans;
}
//用树状数组维护一下就好了
void add(int p, ll v) {
while (p <= cnt) {
tr[p] = (tr[p] + v) % mod;
p += (p & -p);
}
}
ll qur(int p) {
ll ans = 0;
while (p) {
ans += tr[p];
ans %= mod;
p -= (p & -p);
}
return ans;
}
/*ll xs(int i, ll j) {
ll ans = fact[i + 1];
for (ll k = j; k <= j + i; ++k) ans = ans * k % mod;
return ans;
}*/
int main() {
scanf("%d", &n);
fact[0] = 1;
for (int i = 1; i <= n; ++i) {
inv[i] = qp(i, mod - 2);
fact[i] = fact[i - 1] * inv[i] % mod; //预处理,这个 fact 好像没用
}
for (int i = 1; i <= n; ++i) {
scanf("%lld%lld", a + i, b + i);
bian[i * 2 - 1] = a[i];
bian[i * 2] = b[i] + 1;
}
sort(bian + 1, bian + n * 2 + 1);
cnt = unique(bian + 1, bian + n * 2 + 1) - bian - 1; //离散化边
for (int i = 1; i <= n; ++i) {
int x = lower_bound(bian + 1, bian + cnt + 1, a[i]) - bian;
int y = lower_bound(bian + 1, bian + cnt + 1, b[i] + 1) - bian; //找到这个学校的区间
for (int j = y - 1; j >= x; --j) { //从右往左枚举
ll tmp = qur(j - 1); //找到前面要加进来的
++tmp; //由于树状数组没有 0 位,手动加,这样的话最后统计也不用减 1
jia[j].emplace_front(tmp); //存储每次加的
add(j, tmp); //少的那一次
for (int k = 0; k < jia[j].size(); ++k) {
//add(j, jia[j][k] * xs(k, bian[j + 1] - bian[j] - 1) % mod);
//这个就是乘个系数加进去就行,由于是加在头部,k 就代表加进来的时间(次数)
jia[j][k] = jia[j][k] * (bian[j + 1] - bian[j] - 1 + k) % mod * inv[k + 1] % mod;
add(j, jia[j][k]);
}
}
}
printf("%lld", qur(cnt));
return 0;
}

注释掉的是我考试时的代码。其实改的话,哎,不预处理逆的话一行就能搞定,悲伤。
最终时间复杂度

后记

这道题没有切掉纯属眼瞎,经过认真反思我认为我应该改善自己打草稿的格式,更加清除明了,并且下次遇到这种类似的需要仔细思考是否能递推解决。

  • 标题: 「APIO2016」划艇 题解
  • 作者: 混氏新子
  • 创建于 : 2023-07-26 22:41:13
  • 更新于 : 2023-09-24 19:06:57
  • 链接: https://blog.huasushis.cn/2023/「APIO2016」划艇 题解/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
此页目录
「APIO2016」划艇 题解