Loading

根据序列前缀最值构建单调性

本篇灵感来自于ABC E - Somen Nagashi,题目有更好的做法,这里只说关于线段树二分的做法

二分查找是一个比较常见的将 \(O(N)\)的操作优化为 \(O(logN)\),但是前提是这个被操作的序列需要具有单调性,那么在一道题目中,我们是否可以有意人为构建出单调性来供我们进行二分操作呢?

从这道题目来看,我们每次需要找到当前 \(t_i \leq t\) 的所有人中 \(i\) 最小的那个人,那么有一个比较暴力的想法就是我开一个值域线段树,每次把每个人放到他对应的 \(t\) 上,然后每次求前缀最小值就可以,但很可惜这题值域是 \(1e9\)。如果我们每次改变这个序列,似乎这个序列是无序的,没法进行更优的操作,但是如果我们开一个数组\(S, \ S_i=min(t_{1 \to i})\),这样的话\(S\) 就是具有单调性的,并且因为我们要找\(i\) 最靠前的那个人,那么我只需要查找第一个\(S_i\leq t\)\(i\) 就好了,这样就做到了\(O(QlogNlogN)\)

其实这也不是我第一次发现这个技巧,在我知道单调栈这个技巧之前我用\(ST\) 表 + 二分也可以解决找到前面第一个比我小或大的数,其中也用到了这个最值找单调性的技巧,感觉比较好像并且比较巧妙,在此记录一下。

update: 之前还以为发现了不得了的东西 (笑尿了),最近学到了线段树二分,发现这个trick比较普遍而且可以做到\(O(QlogN)\),就是直接在线段树上做二分而不是在外面二分位置,简单来说就是可以线段树维护最大值,然后查询的时候优先递归左儿子,如果左儿子不满足条件再递归右儿子,复杂度是一个 \(logN\) 因为每次只会递归一个区间,总递归次数也就是就是树的高度。在学到这个技巧之前我都是外面二分位置做两个 \(logN\) 的。。。

我cpp写的比较抽象,这里就贴一个java的代码吧。

import java.io.*;
import java.util.*;
public class Main {
    final static int N = 200010;
    static int[] a = new int[N], seg = new int[4*N];
    static void update(int u) {
        seg[u] = Math.max(seg[u*2], seg[u*2+1]);
    }
    static void build(int u, int l, int r) {
        if (l == r) {
            seg[u] = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(u*2, l, mid);
        build(u*2+1, mid+1, r);
        update(u);
    }
    static void change(int u, int l, int r, int p, int d) {
        if (l == r) {
            assert(l == d);
            assert(d >= 0); // 手造的数据保证 d >= 0,当然小于0也可以做只不过需要改一些细节
            seg[u] = seg[u] + d;
            return;
        }
        int mid = (l + r) >> 1;
        if (p <= mid) change(u*2, l, mid, p, d);
        else change(u*2+1, mid+1, r, p, d);
        update(u);
    }
    static int search(int u, int l, int r, int ql, int qr, int d) {
        if (ql == l && qr == r) {
            if (seg[u] < d) return -1;
            if (l == r) return l;
            int mid = (l + r) >> 1;
            if (seg[u*2] >= d) return search(u*2, l, mid, ql, mid, d);
            else return search(u*2+1, mid+1, r, mid+1, qr, d);
        }
        int mid = (l + r) >> 1;
        if (qr <= mid) return search(u*2, l, mid, ql, qr, d);
        else if (ql > mid) return search(u*2+1, mid+1, r, ql, qr, d);
        else {
            int pos = search(u*2, l, mid, ql, mid, d);
            if (pos != -1) return pos;
            else return search(u*2+1, mid+1, r, mid+1, qr, d);
        }
    }
    public static void main(String[] args) throws Exception {
        int n = nextInt(), q = nextInt();
        for (int i = 1; i <= n; i++) a[i] = nextInt();
        build(1, 1, n);
        while (q-->0) {
            int t = nextInt();
            if (t == 1) {
                int l = nextInt(), r = nextInt(), d = nextInt();
                cout.println(search(1, 1, n, l, r, d));
            } else {
                int p = nextInt(), d = nextInt();
                change(1, 1, n, p, d);
            }
        }
        closeAll();
    }
    public static StreamTokenizer cin = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    public static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
    public static int nextInt() throws Exception{ cin.nextToken(); return (int) cin.nval; }
    public static void closeAll() throws Exception { cout.close(); in.close(); out.close(); }
}

我没有找地方交这个题,而是自己写了个暴力随机造了一点数据,跑了一会儿应该是没有问题的。

Problem-set
https://atcoder.jp/contests/abc320/tasks/abc320_e
https://www.spoj.com/problems/THEATRE/

posted @ 2024-02-29 14:16  KakaDBL  阅读(20)  评论(0)    收藏  举报