Jumping steps 题解
https://www.luogu.com.cn/problem/U230162
中文题意
共有 \(n\) 级台阶,编号分别为 \(1\sim n\),令初始位置为 \(0\),现在想要跳到 \(n\)。其中有 \(m\) 个障碍物,\(0,n\) 一定不是障碍物,任意时刻不能位于障碍物位置。每次可以从位置 \(x\) 跳到 \(x+k(k>0)\),若跨过超过 \(S\) 个障碍物得 \(0\) 分,否则得 \(k^2\) 分。最终分数为每一步分数之和。问所有从 \(0\) 跳到 \(n\) 的不同方案的分数之和。
\(n\leq 10^9,m\leq2\times10^6\)。
题解(怎么这么啰嗦?)
注意到一个跳跃方案中的若干次跳跃的贡献是相加的,于是可以考虑拆贡献:枚举跳跃的某一步 \(l\to r(r>l)\),设其单次得分为 \(C(l,r)\),包含它的跳跃方案总数为 \(T(l,r)\),答案显然为 \(\sum\limits_{0\leq l<r\leq n}C(l,r)T(l,r)\)。显然若 \(l\) 或 \(r\) 是障碍物,或 \([l,r]\) 内包含超过 \(S\) 个障碍物,\(C(l,r)=0\),否则 \(C(l,r)=(r-l)^2\);设 \(x\) 为 \([l,r]\) 以外的非障碍物且非 \(0/n\) 的点数,这些点随意选不选,于是 \(T(l,r)=2^x\)。
注意到若 \([l,r]\) 内包含的障碍物数量确定了,则 \(C(l,r)\) 和 \(T(l,r)\) 容易用较为简洁的式子写出。于是考虑枚举 \([l,r]\) 跨越的障碍物区间(该区间长度不超过 \(S\))。可以预见到,跨越至少一个障碍物和不跨越障碍物的情况有很大区别,需要分开来算。以及由于 \(0,n\) 一定要选,为了方便可以暂时将它们设为障碍物(令 \(p_0=0,p_{m+1}=n\)),最后再加上以 \(0/n\) 为一端的贡献。
考虑跨越障碍物区间 \(p_{a\sim b}(a\leq b)\) 的总贡献,显然是
我们至少要 \(\mathrm O(1)\) 计算这玩意。注意到后面这一坨 \(\sum\) 可以通过二维差分,归约到以下形式:
根据数学直觉,容易预见到:\(g(x,y)\) 可以通过若干次平凡的错相相消法得到 \(\mathrm O(1)\) 的计算式。此处直接使用 WolframAlpha 给出的结果。但考虑到在 ACM 比赛中无法使用 WolframAlpha,本篇题解末尾将会给出推导过程。
此时直接枚举障碍物区间 \([a,b]\),使用快速幂即可将这一部分做到 \(\mathrm O\!\left(m^2\log n\right)\) 的复杂度。继续优化。
观察到 \(g(x,y)\) 可以写成三项 \(h_1'(x)h_2'(y)\) 相加的形式,算上 \(2^{b-a}\) 的贡献,令 \(h_1(x)=2^{-a}h_1'(x),h_2(y)=2^{b}h_2'(y)\)。考虑三项分别计算贡献,然后相加。观察到 \(F(a,b)\) 转化到的四个 \(g(\cdot,\cdot)\) 中,后者 \(h_1(x)\) 仅与 \(a\) 有关,\(h_2(y)\) 仅与 \(b\) 有关。于是考虑扫描线:从左往右枚举 \(y\),时刻记录合法的 \(x\) 的 \(h_1(p_x-1)\) 之和 \(s\),与 \(h_1(p_{x-1})\) 之和 \(t\),然后通过 \(u=h_2(p_{y+1}-1),v=h_2(p_y)\) 可以计算出三项中的当前这项对 \(2^{b-a}f(p_{x-1}+1,p_x-1,p_y+1,p_{y+1}-1)\) 的贡献:\(su-sv-tu+tv=(s-t)(u-v)\)。注意还要乘以一个 \(2^{n+1-m-2}\) 的常系数。
如何时刻维护合法的 \(x\) 的 \(h_1(R(x))\) 之和与 \(h_1(L(x))\) 之和?合法的 \(x\) 是一段区间 \([\max(1,y-S+1),y]\),每次右端点 \(+1\),左端点可能不变可能 \(+1\),直接加上 / 扣掉相关贡献就行了。这样算上快速幂,复杂度是 \(\mathrm O(m\log n)\)。由于该算法有十几倍常数,使用快速幂可能会被卡常。但注意到这里面只要求 \(2\) 的若干次幂,可以使用光速幂做到 \(\mathrm O(1)\):设 \(B=\left\lceil\sqrt P\right\rceil\),对所有 \(i\in[0,B)\) 预处理 \(2^i\) 和 \(2^{iB}\),即可 \(\mathrm O(1)\) 询问 \(2^x(x\in[0,P))\)(对于 \(x\notin[0,P)\) 显然可以用费马小定理归约到这个区间)。这样该部分复杂度就是 \(\mathrm O\!\left(m+\sqrt P\right)\)。
剩下两个较为平凡的部分:不跨越任何障碍物的贡献、以 \(0/n\) 为一端的贡献。先考虑后者。以 \(0\) 为左端为例,\(n\) 为右端类似。枚举跨越的最后一个障碍物 \(a\in[0,S]\),贡献显然是
直接调用 \(f\) 函数就行了。还有一个小坑:当 \(s=m\) 时,\(T(0,n)\neq 0\),要额外算上 \(0\to n\) 的贡献。
最后是不跨越任何障碍物的贡献。枚举位置在 \(p_a+1\sim p_{a+1}-1(a\in[0,m])\) 之间,设 \(x=p_{a+1}-p_a-1\),贡献显然为
这是个比 \(g(x,y)\) 简单得多的式子,此处直接给出 WolframAlpha 的结果,推导过程从略。
直接计算即可,复杂度 \(\mathrm O(m)\)。另外,如果不想再推式子,而想沿用之前推导得到的 \(f(\cdot,\cdot,\cdot,\cdot)\) 的结果,此处有一种带 \(\log\) 的方法。虽然有可能被卡常,但笔者认为较有启发性。这个式子其实就是 \(\sum\limits_{1\leq l<r\leq x}2^{l-r}(r-l)^2\)。令 \(M=\lfloor x/2\rfloor\),考虑分治成 \([1,M],[M,x]\) 再加上左右两部分的互相贡献(显然是 \(f(1,M,M,x)\))。若 \(x\) 是偶数,则 \([1,M],[M,x]\) 长度相等,答案也显然相等,所以可以直接归约到 \(2f(M)+f(1,M,M,x)\)。\(x\) 是奇数怎么办呢?可以令 \(x\gets x-1\),即 \(f(x-1)+f(1,1,2,x)\)。这样递归层数为 \(\mathrm O(\log n)\),总复杂度 \(\mathrm O(m\log n)\)。这其实是一个类似倍增快速幂的过程。类似光速幂的方法,这个做法也容易被优化到 \(\mathrm O\!\left(m+\sqrt P\right)\)。
最终总时间复杂度、空间复杂度 \(\mathrm O\!\left(m+\sqrt P\right)\),下面是标程:
#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;
}
附:\(g(x,y)\) 的推导
设 \(A(a,x)=\sum\limits_{i=1}^xa^i\),\(B(a,x)=\sum\limits_{i=1}^xa^ii\),\(C(a,x)=\sum\limits_{i=1}^xa^ii^2\),其中 \(a\neq 1\)。
- \(aA(a,x)-A(a,x)=a^{x+1}-a\),解得 \(A(a,x)=\dfrac{a^{x+1}-a}{a-1}\)。
- \(aB(a,x)-B(a,x)=a^{x+1}x-\sum\limits_{i=1}^xa^i=a^{x+1}x-A(a,x)\),解得 \(B(a,x)=\dfrac{a^{x+2}x-a^{x+1}(x+1)+a}{(a-1)^2}\)。
- \(aC(a,x)-C(a,x)=a^{x+1}x^2-\sum\limits_{i=1}^xa^i(2i-1)=a^{x+1}x^2-2B(a,x)+A(a,x)\),解得 \(C(a,x)=\dfrac{a^{x+3}x^2+a^{x+2}(-2x^2-2x+1)+a^{x+1}(x+1)^2-a^2-a}{(a-1)^3}\)。
于是
这样也将 \(g(x,y)\) 化成了三个 \(h_1(x)h_2(y)\) 之和的形式,跟之前的式子是等效的。适当化简可以减小常数。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在外漂泊的这几年总结和感悟,展望未来
· 博客园 & 1Panel 联合终身会员上线
· 支付宝事故这事儿,凭什么又是程序员背锅?有没有可能是这样的...
· https证书一键自动续期,帮你解放90天限制
· 在 ASP.NET Core WebAPI如何实现版本控制?
2020-09-17 洛谷 P4099 - SAO