题解:P17833 [CSP-S 2026] 迁徙 / migrate
战斗爽!
设 \(prv_i,nxt_i\) 表示从点 \(i\) 向左/向右跳能跳到的最远的点。
考察一对合法的 \(u,v\),显然从 \(u\) 向右贪心跳到 \(v\) 是一条合法路径 \(a_1,\cdots,a_k\)。对于另一条路径 \(b_1,\cdots,b_k\),取最小的 \(i\) 使得 \(a_i>b_i\),则 \(a_i,b_i\) 到 \(v\) 的最短路径都是唯一的。
这启发我们去刻画 \(u,v\) 间最短路唯一的条件。运用一些注意力,观察到 \(u,v\) 间最短路唯一,当且仅当:从 \(u\) 出发向右贪心跳的路径,和从 \(v\) 出发向左贪心跳的路径完全相同。设向左贪心跳的路径反转后为 \(b_1,\cdots,b_k\),则对于任意合法最短路 \(c_1,\cdots,c_k\),不难发现 \(b_i\leq c_i\leq a_i\),由此得证。
继续刻画 \(u,v\) 间恰好有两条最短路的条件。观察到 \(a_1,\cdots,a_i,b_{i+1},\cdots,b_k\) 同样是合法的最短路,因此如果 \(a,b\) 在至少两个位置 \(p<q\) 不同,则 \(a_1,\cdots,a_p,b_{p+1},\cdots,b_k\) 是第三条最短路,不合法。而若 \(a_i-b_i>1\),显然也可以取它们中间的点得到第三条最短路。
于是我们得到了 \(u,v\) 间恰好有两条最短路充要条件:存在一个 \(i\),使得 \(a_i-b_i=1\),且对于每个 \(j\neq i\),\(a_j=b_j\)。
考虑枚举 \(b_i=x\),设 \(x\) 所在的路径向左走一步到达 \(u\),\(x+1\) 所在路径向右走一步到达 \(v\)。显然要有 \(nxt_u=x+1\)。若 \(prv_x<u<x\),则 \(u\) 必须是路径起点;若 \(u=prv_x\),则 \(u\) 能贡献到的路径起点 \(s\) 需要满足:\(s\) 向右贪心跳恰好能跳到 \(u\),且 \(s\) 到 \(u\) 的最短路唯一。设 \(f_i\) 表示 \(i\) 能贡献到的路径起点的个数,容易分讨得到 DP 转移:
正序枚举 \(i\),维护 \((prv_i,i)\) 区间内的桶即可。
类似设 \(g_i\) 表示 \(i\) 能贡献到的路径终点的个数:
倒序枚举 \(i\),维护 \((i,nxt_i)\) 区间内的桶即可。
一个 \(x\) 对应的路径起点的个数为
对应的路径终点的个数为
两个 \(\sum\) 可以在计算 \(f,g\) 的时候顺便得到。
时间复杂度 \(\mathcal{O}(n)\)。
复现了一下场上代码。不算太难写。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128;
using ui = unsigned int;
using ull = unsigned long long;
using u128 = unsigned __int128;
using ld = long double;
using pii = pair<int, int>;
const int MAXN = 2e6 + 5;
template<typename T> T lowbit(T x) { return x & -x; }
template<typename T> void chkMin(T &x, T y) { x = y < x ? y : x; }
template<typename T> void chkMax(T &x, T y) { x = x < y ? y : x; }
constexpr int lg2(ll x) { return 63 ^ __builtin_clzll(x); }
constexpr ll bitCeil(ll x) { return x == 1 ? 1ll : 1ll << lg2(x - 1) + 1; }
int tid, tc, n, prv[MAXN], nxt[MAXN];
int f[MAXN], g[MAXN], buc[MAXN], cl[MAXN], cr[MAXN];
ll d, x[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> tid >> tc;
while (tc--) {
cin >> n >> d;
for (int i = 1; i <= n; ++i) cin >> x[i];
for (int r = 2, l = 1; r <= n; ++r) {
while (l <= r && x[r] - x[l] > d) ++l;
prv[r] = l;
}
for (int l = n - 1, r = n; l; --l) {
while (l <= r && x[r] - x[l] > d) --r;
nxt[l] = r;
}
fill(buc, buc + n + 1, 0);
++buc[nxt[1]];
f[1] = 1;
for (int i = 2, p = 1; i <= n; ++i) {
while (p <= min(prv[i], i - 1)) --buc[nxt[p++]];
f[i] = 1 + buc[i];
if (prv[i] < i && nxt[prv[i]] == i) f[i] += f[prv[i]];
cl[i] = buc[i + 1];
++buc[nxt[i]];
}
fill(buc, buc + n + 1, 0);
++buc[prv[n]];
g[n] = 1;
for (int i = n - 1, p = n; i; --i) {
while (p >= max(nxt[i], i + 1)) --buc[prv[p--]];
g[i] = 1 + buc[i];
if (nxt[i] > i && prv[nxt[i]] == i) g[i] += g[nxt[i]];
cr[i] = buc[i - 1];
++buc[prv[i]];
}
ll ans = 0;
for (int i = 2; i < n - 1; ++i) {
int x = cl[i] + (prv[i] < i && nxt[prv[i]] == i + 1 ? f[prv[i]] : 0);
int y = cr[i + 1] + (nxt[i + 1] > i + 1 && prv[nxt[i + 1]] == i ? g[nxt[i + 1]] : 0);
ans += (ll)x * y;
}
cout << ans << '\n';
}
return 0;
}

浙公网安备 33010602011771号