Loading

Luogu P2757 [国家集训队] 等差子序列 题解

Link

好题。考虑简化题意:找一个 \(|p| \geq 3\) 的序列 \(p\) 使得 \(A_{p_i}\) 呈现等差数列,那我们找一个三个数的等差数列就可以了。中间值是特殊的,我们枚举中间值,判断左右两边是否存在一个公差相等的等差数列项。

用到一个有用的 \(01\) 序列转换 Trick。我们从左到右遍历 \(A\),同时维护出一个 \(01\)\(b\),每次扫描到 \(A_i\) 就将 \(b\) 中对应的 \(i\) 位设置成 \(1\)。考虑一组等差数列存在且公差为 \(k\),这意味着 \(A_i - k, A_i + k \in [1, n]\) 同时前一项出现了,后一项尚未出现,即 \(b_{A_i - k} = 1\)\(b_{A_i + k} = 0\),此时 \(A_i - k, A_i, A_i + k\) 顺序排列形成公差为 \(k\) 的长度为 \(3\) 的等差数列。

现在的问题是怎么维护或者说高效判断出有存在于左右两边的符合公差要求的项。我们再次转化问题为检查 \(b\) 是否关于 \(A_i\) 呈现中心对称。如果以 \(A_i\) 为中心的对称区间不是回文,则存在某个 \(k\) 使得 \(A_i - k\)\(A_i + k\) 一个在左一个在右,反之如果是回文的,意味着对于每个 \(k\)\(A_i - k, A_i + k\) 要么都出现要么都不出现。

现在新的问题是如何快速判断回文串?常规的,使用字符串哈希。维护一棵线段树支持带修查询的字符串哈希值,对于 \(A_i\),我们计算正序哈希 \(H_{[A_i - k, A_i - 1]}\) 和逆序哈希 \(H'_{[A_i + 1, A_i + k]}\),如果 \(H = H'\) 则存在回文。实现上,注意逆序哈希的线段树合并顺序。

#include <bits/stdc++.h>

using i64 = long long;
using u64 = unsigned long long;

constexpr int N = 6e5 + 7;
constexpr u64 B = 13331;

int n;
int a[N];
u64 b[N];

struct SegTree {
    struct node {
        int l, r;
        u64 v1, v2;
    } tr[N << 2];
    
    #define ls(o) (o << 1)
    #define rs(o) (o << 1 | 1)
    
    void pushup(const int o) {
        int sizr = tr[rs(o)].r - tr[rs(o)].l + 1;
        int sizl = tr[ls(o)].r - tr[ls(o)].l + 1;
        tr[o].v1 = tr[ls(o)].v1 * b[sizr] + tr[rs(o)].v1;
        tr[o].v2 = tr[rs(o)].v2 * b[sizl] + tr[ls(o)].v2;
    }
    
    void build(int o, int l, int r) {
        tr[o].l = l, tr[o].r = r; tr[o].v1 = tr[o].v2 = 0;
        if (l == r) {
            return;
        }
        int mid = (l + r) >> 1;
        build(ls(o), l, mid);
        build(rs(o), mid + 1, r);
    }

    void update(int o, int l, int r, int p) {
        if (l == r) {
            tr[o].v1 = tr[o].v2 = 1;
            return;
        }
        int mid = (l + r) >> 1;
        if (p <= mid)
            update(ls(o), l, mid, p);
        else
            update(rs(o), mid + 1, r, p);
        pushup(o);
    }
    
    u64 query1(int o, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) {
            return tr[o].v1 * b[qr - r];
        }
        int mid = (l + r) >> 1;
        u64 res = 0;
        if (ql <= mid)
            res += query1(ls(o), l, mid, ql, qr);
        if (qr > mid)
            res += query1(rs(o), mid + 1, r, ql, qr);
        return res;
    }
    
    u64 query2(int o, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) {
            return tr[o].v2 * b[l - ql];
        }
        int mid = (l + r) >> 1;
        u64 res = 0;
        if (qr > mid)
            res += query2(rs(o), mid + 1, r, ql, qr);
        if (ql <= mid)
            res += query2(ls(o), l, mid, ql, qr);
        return res;
    }
} seg;

void init() {
    b[0] = 1;
    for (int i = 1; i < N; i++) {
        b[i] = b[i - 1] * B;
    }
}

void solve() {
    std::cin >> n;
    for (int i = 1; i <= n; i++) {
        std::cin >> a[i];
    }
    seg.build(1, 1, n);
    for (int i = 1; i <= n; i++) {
        int t = a[i]; 
        int len = std::min(t - 1, n - t);
        if (len > 0) {
            u64 h1 = seg.query1(1, 1, n, t - len, t - 1);
            u64 h2 = seg.query2(1, 1, n, t + 1, t + len);
            if (h1 != h2) {
                std::cout << "Y\n";
                return;
            }
        }
        seg.update(1, 1, n, t);
    }
    std::cout << "N\n";
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int t;
    std::cin >> t;
    init();
    while (t--) {
        solve();
    }
    return 0;
}
posted @ 2025-11-03 22:01  夢回路  阅读(5)  评论(0)    收藏  举报