loading...

[CSP-S 2026] 迁徙

大家好,我不喜欢 DP,所以场上对着这个 sb 玩意调了 3h

考虑这一类图的可达点是一段区间,对于每个点 \(u\) 我们求出 \([L_u, R_u]\) 表示可达范围。

每个双径对有恰好两条最短路径,我们发现其中一条就是 \(x \to R_x \to R_{R_x} \to\dots \to y\),除最后一步以外都跳 \(R_i\) 必然最优。设中间点为 \(\{a_i\}\),不妨令 \(a_0 = x\),则 \(\forall 1 \le i < dis, a_i = R_{a_{i - 1}}\)。

在这一条的基础上,显然的性质是另一条的中间点恰好有一个 \(a_i\) 不一样。考虑差的应该也不多,这个不一样的位置 \(a_i\) 必定是往前移动了 \(1\)(\(a_i' = a_i - 1\)),证明很显然,因为如果差了超过 \(1\),则考虑 \(z = a_i - 1\) 位置,有三条合法路径 \(a_{i-1} \to z \to a_{i+1}, a_{i-1} \to a_i \to a_{i+1}, a_{i-1} \to a_i' \to a_{i+1}\),超过 \(2\) 个路径。

性质分析得差不多了,考虑枚举这恰好一个不同的位置 \(j\),分析其前后两个位置 \(i, k\)。暂不考虑边界,假设 \(i, k\) 不是起点和终点。

枚举 \(i\),则 \(j = R_i, k = R_j\),钦定 \(i \leadsto k\) 有两种跳法,则跳法分别为 \(i \to j - 1 \to k, i\to j \to k\),所以 \(L_k = j - 1\)。考虑如果 \(L_{j - 1} < i\),则考虑 \(i\) 前一个位置的跳跃,可以直接跳至 \(j - 1\),再跳至 \(k\);\(L_j < i\) 也同理,只是 \(L_{j- 1}\) 更紧些。稍微写一下你会发现 wawawa。于是我对着这个东西调了 3h,如何呢。

开始实现:

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define drep(i, r, l) for (int i = r; i >= l; --i)
#define LL long long
#define FASTIO ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define FRE(p) freopen(#p".in", "r", stdin), freopen(#p".out", "w", stdout)
#define eb emplace_back
#define ld lower_bound
#define ud upper_bound
#define PII pair<int,int>
using namespace std;
const int N = 2e6+5;
int n, nxt[N], pre[N];
LL ans[N], cnt[N];
LL d, a[N];
inline void solve() {
    cin >> n >> d;
    rep(i, 1, n) cin >> a[i];
    rep(i, 1, n) nxt[i] = ud(a + 1, a + n + 1, a[i] + d) - a - 1, pre[i] = ld(a + 1, a + n + 1, a[i] - d) - a;
    LL ret = 0;
    drep(i, n, 1) {
        int j = nxt[i], k = nxt[j];
        cnt[i] = j - max(i - 1, nxt[i - 1]);
        if (i < j && pre[j] == i) {
            cnt[i] += cnt[j];
            if (i < j - 1 && j < k && pre[k] == j - 1) {
                if (pre[j - 1] == i) ans[i] += cnt[k] + 1;
                else ret += cnt[k];
            }
        } else if (i < j - 1 && j < k && pre[k] == j - 1) ret += cnt[k];
    }
    int L = 1, R = 1;
    rep(i, 1, n) {
        int j = nxt[i];
        while (L <= n && pre[L] < j - 1) ++L;
        while (R <= n && pre[R] <= j - 1) ++R; 
        if (i < j - 1) {
            if (pre[j] == i && pre[j - 1] == i) ans[i] += max(0, min(R, nxt[j]) - max(L - 1, j) - 1);
            else ret += max(0, min(R - 1, nxt[j]) - max(L - 1, j));
        }
    }
    drep(i, n, 1) {
        ret += nxt[i] ^ i ? ans[nxt[i]] + ans[i] : ans[i];
        if (i < nxt[i] && pre[nxt[i]] == i) ans[i] += ans[nxt[i]]; 
    }
    rep(i, 1, n) ans[i] = cnt[i] = 0;
    cout << ret << '\n';
}
signed main() {
    FASTIO;
#ifdef LOCAL
    FRE(test);
#endif
    int tid, _;
    cin >> tid >> _;
    while (_--) solve();
    return 0;
}

小细节只需要对着 3,4 大样例调 3h+2h 就能写出来了,不是什么难点。不过也好,就算场上过了,我也刻不出来。

posted @ 2026-11-02 21:40  goldspade  阅读(6)  评论(1)    收藏  举报