Jumping steps 题解
https://www.luogu.com.cn/problem/U230162
中文题意
共有 级台阶,编号分别为 ,令初始位置为 ,现在想要跳到 。其中有 个障碍物, 一定不是障碍物,任意时刻不能位于障碍物位置。每次可以从位置 跳到 ,若跨过超过 个障碍物得 分,否则得 分。最终分数为每一步分数之和。问所有从 跳到 的不同方案的分数之和。
。
题解(怎么这么啰嗦?)
注意到一个跳跃方案中的若干次跳跃的贡献是相加的,于是可以考虑拆贡献:枚举跳跃的某一步 ,设其单次得分为 ,包含它的跳跃方案总数为 ,答案显然为 。显然若 或 是障碍物,或 内包含超过 个障碍物,,否则 ;设 为 以外的非障碍物且非 的点数,这些点随意选不选,于是 。
注意到若 内包含的障碍物数量确定了,则 和 容易用较为简洁的式子写出。于是考虑枚举 跨越的障碍物区间(该区间长度不超过 )。可以预见到,跨越至少一个障碍物和不跨越障碍物的情况有很大区别,需要分开来算。以及由于 一定要选,为了方便可以暂时将它们设为障碍物(令 ),最后再加上以 为一端的贡献。
考虑跨越障碍物区间 的总贡献,显然是
我们至少要 计算这玩意。注意到后面这一坨 可以通过二维差分,归约到以下形式:
根据数学直觉,容易预见到: 可以通过若干次平凡的错相相消法得到 的计算式。此处直接使用 WolframAlpha 给出的结果。但考虑到在 ACM 比赛中无法使用 WolframAlpha,本篇题解末尾将会给出推导过程。
此时直接枚举障碍物区间 ,使用快速幂即可将这一部分做到 的复杂度。继续优化。
观察到 可以写成三项 相加的形式,算上 的贡献,令 。考虑三项分别计算贡献,然后相加。观察到 转化到的四个 中,后者 仅与 有关, 仅与 有关。于是考虑扫描线:从左往右枚举 ,时刻记录合法的 的 之和 ,与 之和 ,然后通过 可以计算出三项中的当前这项对 的贡献:。注意还要乘以一个 的常系数。
如何时刻维护合法的 的 之和与 之和?合法的 是一段区间 ,每次右端点 ,左端点可能不变可能 ,直接加上 / 扣掉相关贡献就行了。这样算上快速幂,复杂度是 。由于该算法有十几倍常数,使用快速幂可能会被卡常。但注意到这里面只要求 的若干次幂,可以使用光速幂做到 :设 ,对所有 预处理 和 ,即可 询问 (对于 显然可以用费马小定理归约到这个区间)。这样该部分复杂度就是 。
剩下两个较为平凡的部分:不跨越任何障碍物的贡献、以 为一端的贡献。先考虑后者。以 为左端为例, 为右端类似。枚举跨越的最后一个障碍物 ,贡献显然是
直接调用 函数就行了。还有一个小坑:当 时,,要额外算上 的贡献。
最后是不跨越任何障碍物的贡献。枚举位置在 之间,设 ,贡献显然为
这是个比 简单得多的式子,此处直接给出 WolframAlpha 的结果,推导过程从略。
直接计算即可,复杂度 。另外,如果不想再推式子,而想沿用之前推导得到的 的结果,此处有一种带 的方法。虽然有可能被卡常,但笔者认为较有启发性。这个式子其实就是 。令 ,考虑分治成 再加上左右两部分的互相贡献(显然是 )。若 是偶数,则 长度相等,答案也显然相等,所以可以直接归约到 。 是奇数怎么办呢?可以令 ,即 。这样递归层数为 ,总复杂度 。这其实是一个类似倍增快速幂的过程。类似光速幂的方法,这个做法也容易被优化到 。
最终总时间复杂度、空间复杂度 ,下面是标程:
#include <bits/stdc++.h>
using namespace std;
#define REP(i, l, r) for(int i = (l); i <= (r); ++i)
using ll = long long;
template<class T = int> T read() {
T x = 0; char c = getchar(); bool ne = false;
while(!isdigit(c)) ne |= c == '-', c = getchar();
while(isdigit(c)) x = 10 * x + (c ^ 48), c = getchar();
return ne ? -x : x;
}
constexpr int P = 1e9 + 7;
void addto(int &x, int y) { x += y, x >= P && (x -= P), x < 0 && (x += P); }
int add(int x, int y) { return x < 0 && (x += P), x += y, x >= P ? x - P : x < 0 ? x + P : x; }
struct power_t {
static constexpr int B = 32768;
int pw0[B | 10], pw1[B | 10];
power_t() {
pw0[0] = pw1[0] = 1;
REP(i, 1, B - 1) pw0[i] = add(pw0[i - 1], pw0[i - 1]);
int base = add(pw0[B - 1], pw0[B - 1]);
REP(i, 1, B - 1) pw1[i] = (ll)pw1[i - 1] * base % P;
}
int operator()(ll x) {
x = (x % (P - 1) + P - 1) % (P - 1);
return (ll)pw1[x >> 15] * pw0[x & (B - 1)] % P;
}
} power;
constexpr int N = 2e6 + 10;
int n, m, S;
int p[N];
int ans = 0;
int g(int x, int y) {
return (ll)add((ll)power(x) * x % P * x % P * add(power(y), -1) % P,
add((ll)add(power(x), -1) * add(-(ll)y * y % P, add(-6ll * y % P, 13ll * add(power(y), -1) % P)) % P,
(ll)power(x + 1) * x % P * add(y, add(-3ll * power(y) % P, 3)) % P)) * power(1 - y) % P;
}
int f(int l1, int r1, int l2, int r2) {
return add(g(r1, r2), add(-g(r1, l2 - 1), add(-g(l1 - 1, r2), g(l1 - 1, l2 - 1))));
}
void solve_different_block() {
int s1 = 0, s2 = 0, s3 = 0, t1 = 0, t2 = 0, t3 = 0;
auto insl = [&](int coef, int x, int &a, int &b, int &c) {
addto(a, (ll)coef * power(x) % P * x % P * x % P);
addto(b, (ll)coef * add(power(x), -1) % P);
addto(c, (ll)coef * power(x + 1) % P * x % P);
};
int u1 = 0, u2 = 0, u3 = 0, v1 = 0, v2 = 0, v3 = 0;
auto insr = [&](int y, int &a, int &b, int &c) {
a = (ll)add(power(y), -1) * power(1 - y) % P;
b = (ll)add(-(ll)y * y % P, add(-6ll * y % P, 13ll * add(power(y), -1) % P)) * power(1 - y) % P;
c = (ll)add(y, add(-3ll * power(y) % P, 3)) * power(1 - y) % P;
};
auto addans = [&](int coef, int s, int t, int u, int v) {
addto(ans, (ll)coef * add(s, -t) % P * add(u, -v) % P);
};
REP(i, 1, m) {
int coef = power(-i);
insl(coef, p[i] - 1, s1, s2, s3), insl(coef, p[i - 1], t1, t2, t3);
if(i > S) {
coef = -power(-(i - S));
insl(coef, p[i - S] - 1, s1, s2, s3), insl(coef, p[i - S - 1], t1, t2, t3);
}
insr(p[i + 1] - 1, u1, u2, u3), insr(p[i], v1, v2, v3);
coef = power((ll)n + 1 - m - 2 + i);
addans(coef, s1, t1, u1, v1), addans(coef, s2, t2, u2, v2), addans(coef, s3, t3, u3, v3);
}
}
void solve_inside_block() {
/* O(m log n) method
function<int(int)> calc = [&](int x) {
if(x <= 1) return 0;
if(x & 1) {
return add(calc(x - 1), f(1, 1, 2, x));
} else {
int mid = x >> 1;
return add(f(1, mid, mid + 1, x), 2 * calc(mid) % P);
}
};
REP(i, 0, m) {
int len = p[i + 1] - p[i] - 1;
int coef = power((ll)n + 1 - m - 2 - 1);
addto(ans, (ll)coef * calc(len) % P);
}
*/
REP(i, 0, m) {
int x = p[i + 1] - p[i] - 1;
int fx = (ll)power(1 - x) * add((ll)x * x % P, add(3ll * add(power(x), 2) * x % P, -13ll * add(power(x), -1) % P)) % P;
int coef = power((ll)n + 1 - m - 2 - 1);
addto(ans, (ll)coef * fx % P);
}
}
void solve_0_n() {
REP(i, 0, S) {
int coef = power((ll)n - m - 1 + i);
addto(ans, (ll)coef * f(0, 0, p[i] + 1, p[i + 1] - 1) % P);
addto(ans, (ll)coef * f(p[m - i] + 1, p[m - i + 1] - 1, n, n) % P);
}
if(S == m) addto(ans, (ll)n * n % P);
}
int main() {
n = read(), m = read(), S = read();
REP(i, 1, m) p[i] = read();
p[0] = 0, p[m + 1] = n;
solve_different_block();
solve_inside_block();
solve_0_n();
cout << ans << "\n";
return 0;
}
附: 的推导
设 ,,,其中 。
- ,解得 。
- ,解得 。
- ,解得 。
于是
这样也将 化成了三个 之和的形式,跟之前的式子是等效的。适当化简可以减小常数。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
2020-09-17 洛谷 P4099 - SAO