Jumping steps 题解

https://www.luogu.com.cn/problem/U230162

中文题意

共有 n 级台阶,编号分别为 1n,令初始位置为 0,现在想要跳到 n。其中有 m 个障碍物,0,n 一定不是障碍物,任意时刻不能位于障碍物位置。每次可以从位置 x 跳到 x+k(k>0),若跨过超过 S 个障碍物得 0 分,否则得 k2 分。最终分数为每一步分数之和。问所有从 0 跳到 n 的不同方案的分数之和。

n109,m2×106

题解(怎么这么啰嗦?)

注意到一个跳跃方案中的若干次跳跃的贡献是相加的,于是可以考虑拆贡献:枚举跳跃的某一步 lr(r>l),设其单次得分为 C(l,r),包含它的跳跃方案总数为 T(l,r),答案显然为 0l<rnC(l,r)T(l,r)。显然若 lr 是障碍物,或 [l,r] 内包含超过 S 个障碍物,C(l,r)=0,否则 C(l,r)=(rl)2;设 x[l,r] 以外的非障碍物且非 0/n 的点数,这些点随意选不选,于是 T(l,r)=2x

注意到若 [l,r] 内包含的障碍物数量确定了,则 C(l,r)T(l,r) 容易用较为简洁的式子写出。于是考虑枚举 [l,r] 跨越的障碍物区间(该区间长度不超过 S)。可以预见到,跨越至少一个障碍物和不跨越障碍物的情况有很大区别,需要分开来算。以及由于 0,n 一定要选,为了方便可以暂时将它们设为障碍物(令 p0=0,pm+1=n),最后再加上以 0/n 为一端的贡献。

考虑跨越障碍物区间 pab(ab) 的总贡献,显然是

F(a,b)=i=pa1+1pa1j=pb+1pb+112n+1(m+2)+(ba+1)(ji+1)(ji)2=2n+1m2+bai=pa1+1pa1j=pb+1pb+112ij(ji)2

我们至少要 O(1) 计算这玩意。注意到后面这一坨 可以通过二维差分,归约到以下形式:

g(x,y)=i=1xj=1y2ij(ji)2f(l1,r1,l2,r2)=g(r1,r2)g(r1,l21)g(l11,r2)+g(l11,l21)F(a,b)=2n+1m22baf(pa1+1,pa1,pb+1,pb+11)

根据数学直觉,容易预见到:g(x,y) 可以通过若干次平凡的错相相消法得到 O(1) 的计算式。此处直接使用 WolframAlpha 给出的结果。但考虑到在 ACM 比赛中无法使用 WolframAlpha,本篇题解末尾将会给出推导过程。

g(x,y)=\textcolorred2xx2\textcolorgreen(2y1)21y+\textcolorred(2x1)\textcolorgreen(y26y+13(2y1))21y+\textcolorred2x+1x\textcolorgreen(y3×2y+3)21y

此时直接枚举障碍物区间 [a,b],使用快速幂即可将这一部分做到 O(m2logn) 的复杂度。继续优化。

观察到 g(x,y) 可以写成三项 h1(x)h2(y) 相加的形式,算上 2ba 的贡献,令 h1(x)=2ah1(x),h2(y)=2bh2(y)。考虑三项分别计算贡献,然后相加。观察到 F(a,b) 转化到的四个 g(,) 中,后者 h1(x) 仅与 a 有关,h2(y) 仅与 b 有关。于是考虑扫描线:从左往右枚举 y,时刻记录合法的 xh1(px1) 之和 s,与 h1(px1) 之和 t,然后通过 u=h2(py+11),v=h2(py) 可以计算出三项中的当前这项对 2baf(px1+1,px1,py+1,py+11) 的贡献:susvtu+tv=(st)(uv)。注意还要乘以一个 2n+1m2 的常系数。

如何时刻维护合法的 xh1(R(x)) 之和与 h1(L(x)) 之和?合法的 x 是一段区间 [max(1,yS+1),y],每次右端点 +1,左端点可能不变可能 +1,直接加上 / 扣掉相关贡献就行了。这样算上快速幂,复杂度是 O(mlogn)。由于该算法有十几倍常数,使用快速幂可能会被卡常。但注意到这里面只要求 2 的若干次幂,可以使用光速幂做到 O(1):设 B=P,对所有 i[0,B) 预处理 2i2iB,即可 O(1) 询问 2x(x[0,P))(对于 x[0,P) 显然可以用费马小定理归约到这个区间)。这样该部分复杂度就是 O(m+P)

剩下两个较为平凡的部分:不跨越任何障碍物的贡献、以 0/n 为一端的贡献。先考虑后者。以 0 为左端为例,n 为右端类似。枚举跨越的最后一个障碍物 a[0,S],贡献显然是

i=pa+1pa+112nm1i+ai2=2nm1+af(0,0,pa+1,pa+11)

直接调用 f 函数就行了。还有一个小坑:当 s=m 时,T(0,n)0,要额外算上 0n 的贡献。

最后是不跨越任何障碍物的贡献。枚举位置在 pa+1pa+11(a[0,m]) 之间,设 x=pa+1pa1,贡献显然为

i=1x(xi)2n+1m2(i+1)i2=2n+1m21i=1x(xi)2ii2

这是个比 g(x,y) 简单得多的式子,此处直接给出 WolframAlpha 的结果,推导过程从略。

i=1x2ii2(xi)=21x(x2+3(2x+2)x13(2x1))

直接计算即可,复杂度 O(m)。另外,如果不想再推式子,而想沿用之前推导得到的 f(,,,) 的结果,此处有一种带 log 的方法。虽然有可能被卡常,但笔者认为较有启发性。这个式子其实就是 1l<rx2lr(rl)2。令 M=x/2,考虑分治成 [1,M],[M,x] 再加上左右两部分的互相贡献(显然是 f(1,M,M,x))。若 x 是偶数,则 [1,M],[M,x] 长度相等,答案也显然相等,所以可以直接归约到 2f(M)+f(1,M,M,x)x 是奇数怎么办呢?可以令 xx1,即 f(x1)+f(1,1,2,x)。这样递归层数为 O(logn),总复杂度 O(mlogn)。这其实是一个类似倍增快速幂的过程。类似光速幂的方法,这个做法也容易被优化到 O(m+P)

最终总时间复杂度、空间复杂度 O(m+P),下面是标程:

#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)=i=1xaiB(a,x)=i=1xaiiC(a,x)=i=1xaii2,其中 a1

  • aA(a,x)A(a,x)=ax+1a,解得 A(a,x)=ax+1aa1
  • aB(a,x)B(a,x)=ax+1xi=1xai=ax+1xA(a,x),解得 B(a,x)=ax+2xax+1(x+1)+a(a1)2
  • aC(a,x)C(a,x)=ax+1x2i=1xai(2i1)=ax+1x22B(a,x)+A(a,x),解得 C(a,x)=ax+3x2+ax+2(2x22x+1)+ax+1(x+1)2a2a(a1)3

于是

g(x,y)=i=1xj=1y2ij(ji)2=i=1x2ij=1y2j(i22ij+j2)=\textcolorredC(2,x)\textcolorgreenA(1/2,y)\textcolorred2B(2,x)\textcolorgreenB(1/2,y)\textcolorred+A(2,x)\textcolorgreenC(1/2,y)

这样也将 g(x,y) 化成了三个 h1(x)h2(y) 之和的形式,跟之前的式子是等效的。适当化简可以减小常数。

posted @   ycx060617  阅读(251)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
历史上的今天:
2020-09-17 洛谷 P4099 - SAO
点击右上角即可分享
微信分享提示