CF 452F Permution 神奇的线段树判断

题目大意

给定一个 1 N的列,问是否存在一个长度为 3 的子等差数列。

N300000

解题思路

看到这题,感觉有点无从下手,好像怎么要都要枚举两个值。

但是我们可以发现一个很神奇的性质如果对于一个位置 i ,它是子等差序列中的第二项,那么如果不存在长度为3的子等差数列,那么 Ai+k Aik 在位置 i 前肯定是成对存在的,即如果我们把位置i前出现的数都在数组上打上 tag ,那么这些 tag 肯定是关于 Ai 对称的(在边界之内)!那么我们可以用 Hash 来维护数组中连续一段数的存在情况,但是对于数组中任意一个位置都要判断是否合法。那么只需在线段树上打 tag ,然后线段树中每个结点存的是 Hash 值,那么从前往后,边维护边判断即可。

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long LL;

const int Mo = 1e9 + 7;
const int MAXN = 3e5 + 5;

struct Node {
    LL Num;
    int len;
    Node (LL num, int Len) {Num = num, len = Len;}
    Node (){}
} Tr[2][MAXN * 4];

int N;
LL Pow[MAXN];

Node Merge(Node L, Node R) {
    Node New;
    New.len = L.len + R.len;
    New.Num = (1ll * L.Num * Pow[R.len] + R.Num) % Mo;
    return New;
}

void Build(int Ord, int Now, int l, int r) {
    Tr[Ord][Now].len = r - l + 1;
    if (l == r) return;
    int Mid = (l + r) >> 1;
    Build(Ord, Now * 2, l, Mid), Build(Ord, Now * 2 + 1, Mid + 1, r);
}

void Modify(int Ord, int Now, int l, int r, int Side) {
    if (l == r) {
        Tr[Ord][Now].Num = 1;
        return;
    }
    int Mid = (l + r) >> 1;
    if (Side <= Mid) Modify(Ord, Now * 2, l, Mid, Side); else
        Modify(Ord, Now * 2 + 1, Mid + 1, r, Side);
    if (!Ord) Tr[Ord][Now] = Merge(Tr[Ord][Now * 2], Tr[Ord][Now * 2 + 1]); else
        Tr[Ord][Now] = Merge(Tr[Ord][Now * 2 + 1], Tr[Ord][Now * 2]);
}

Node Query(int Ord, int Now, int l, int r, int lx, int rx) {
    if (lx > rx) return Node(0, 0);
    if (l == lx && r == rx) return Tr[Ord][Now];
    int Mid = (l + r) >> 1;
    if (rx <= Mid) return Query(Ord, Now * 2, l, Mid, lx, rx); else
    if (lx > Mid) return Query(Ord, Now * 2 + 1, Mid + 1, r, lx, rx); else {
        Node L = Query(Ord, Now * 2, l, Mid, lx, Mid);
        Node R = Query(Ord, Now * 2 + 1, Mid + 1, r, Mid + 1, rx);
        if (!Ord) return Merge(L, R); else 
            return Merge(R, L);
    }
}

int main() {
    freopen("data.in", "r", stdin), freopen("data.out", "w", stdout);

    scanf("%d", &N);
    Pow[0] = 1;
    Build(0, 1, 1, N), Build(1, 1, 1, N);
    for (int i = 1; i <= N; i ++) Pow[i] = 1ll * Pow[i - 1] * 2 % Mo;
    for (int i = 1; i <= N; i ++) {
        int Now;
        scanf("%d", &Now);
        int len = min(Now - 1, N - Now);
        if (Query(0, 1, 1, N, Now - len, Now - 1).Num != Query(1, 1, 1, N, Now + 1, Now + len).Num) {
            printf("YES\n");
            return 0;
        }
        Modify(0, 1, 1, N, Now);
        Modify(1, 1, 1, N, Now);
    }
    printf("NO\n");
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值