「Bombs」Solution

题意简述

给定两个长度为 n n n 的排列 a , b a ,b a,b,对于一个初始为空的集合进行以下操作:对于每个 i i i,将 a i a_i ai 加入集合,若 v i s i = 1 vis_i=1 visi=1,将此时集合中的最大值删除。
现对于每一个 i i i,求出 ∀ j ∈ [ 1 , i − 1 ] , v i s j = 1 \forall j \in [1 , i - 1],vis_j=1 j[1,i1]visj=1 的情况下,进行一次操作后集合中的最大值。

  • 1 ≤ n ≤ 3 × 1 0 5 1 \le n \le 3 \times 10^5 1n3×105

思路

不妨令 a n s i ans_i ansi i i i 的答案,由于只有删除操作,显然有 a n s i + 1 ≤ a n s i ans_{i+1} \le ans_i ansi+1ansi
可以很自然的想到,我们可以 O ( n ) O(n) O(n) n n n 1 1 1 扫一遍,对于每一个 i i i 判断当前枚举到的 x x x 合不合法,如果不合法就 x ← x − 1 x \leftarrow x-1 xx1,继续判断即可。

所以现在的瓶颈便在于:该如何快速判断当前枚举到的 x x x 合不合法呢?

  • Hint1: x x x 合法意味着什么?
  • Hint2:一个数 y y y 已被删除的条件是什么?
  • Hint3:需要删除的数和标记点有什么联系?

不妨从以上 tips \text{tips} tips 入手。如果 x x x 合法,那么 x x x 一定未被删除,且 ∀ y ∈ [ x + 1 , n ] \forall y \in [x + 1 , n] y[x+1,n] 都已被删除。 y y y 被删除的充要条件是 y y y 所处的位置后面有一个标记点可以用来删除 y y y。想到这,便不难发现其实判断的本质就是在寻找标记点与待删除点的 配对关系,类似于一个 括号序列

不妨将一个待删除点 y ∈ [ x + 1 , n ] y \in [x + 1 , n] y[x+1,n] 视作左括号,将一个标记点视作右括号,那么原问题便转化为括号序列中,每一个 左括号是否都有与之对应的右括号 的匹配问题。

注意在此题中,一个位置上既可以有左括号,也可以有右括号(一个点既可以待删除,也可以被标记。)且此题中括号匹配存在优先级的概念,即 y ∈ [ x + 1 , n ] y \in [x+1,n] y[x+1,n] 会比 x x x 优先匹配右括号。通俗地讲,如果想给当前 x x x 分配右括号,那么必须先匹配完比他大的数才可以。

想想普通的括号匹配问题,如果对于任意位置 i i i [ 1 , i ] [1 , i] [1,i] 中左括号的数量大于等于 [ 1 , i ] [1,i] [1,i] 中右括号的数量,且 [ 1 , n ] [1,n] [1,n] 中左括号与右括号序列数量相等才能匹配。对于这道题,我们不要求匹配,只需要左括号配对完即可,右括号过剩也是被允许的。因此,如果对于任意 i i i [ i , n ] [i,n] [i,n] 中左括号的数量都小于等于 [ i , n ] [i,n] [i,n] 中右括号的数量,那么便说明 [ i , n ] [i,n] [i,n] 中的右括号不仅可以把 [ i + 1 , n ] [i+1,n] [i+1,n] 中的左括号匹配完,且可以匹配到当前 i i i 位置上的左括号。

所以, x x x 不合法(即被删除) 的充要条件为:对于任意 i i i [ i , n ] [i,n] [i,n] 中左括号的数量小于 [ i , n ] [i,n] [i,n] 中右括号的数量。也就等价于 [ i , n ] [i,n] [i,n] 中左括号的数量减去 [ i , n ] [i,n] [i,n] 中右括号的数量小于等于 0 0 0。所以,我们将左括号视为 1 1 1,右括号视为 − 1 -1 1,用线段树维护这个差值即可,添加括号就修改后缀,最后判断全局最大值 M a x Max Max 是否 ≤ 0 \le0 0 即可。

代码

代码也很简洁易懂。

#include<bits/stdc++.h>
#define int long long
const int MAXN = 3e5 + 5;
using namespace std;
int n , a[MAXN] , b[MAXN] , id[MAXN];
namespace Segment{
    struct tree{
        int l , r , Max , val , add;
    }tree[MAXN << 3];
    void pushup(int p) {tree[p].Max = max(tree[p << 1].Max , tree[p << 1 | 1].Max);}
    void pushdown(int p) {
        if (tree[p].add) {
            tree[p << 1].Max += tree[p].add , tree[p << 1 | 1].Max += tree[p].add;
            tree[p << 1].add += tree[p].add , tree[p << 1 | 1].add += tree[p].add;
            tree[p].add = 0;
        }
    }
    void build(int p , int l , int r) {
        tree[p].l = l , tree[p].r = r;
        if (l == r) return;
        int mid = l + r >> 1;
        build(p << 1 , l , mid) , build(p << 1 | 1 , mid + 1 , r);
    }
    void update(int p , int l , int r , int v) {
        if (tree[p].l >= l && tree[p].r <= r) {
            tree[p].Max += v , tree[p].add += v;
            return;
        }
        pushdown(p);
        int mid = tree[p].l + tree[p].r >> 1;
        if (l <= mid) update(p << 1 , l , r , v);
        if (r > mid) update(p << 1 | 1 , l , r , v);
        pushup(p);
    }
}
using namespace Segment;
bool check(int x) {return (tree[1].Max <= 0);}
void remove(int x) {update(1 , 1 , id[x] , 1);}
void add(int x) {update(1 , 1 , x , -1);}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr) , cout.tie(nullptr);
    cin >> n;
    build(1 , 1 , n);
    for (int i = 1 ; i <= n ; i ++) cin >> a[i] , id[a[i]] = i;
    for (int i = 1 ; i <= n ; i ++) cin >> b[i];
    remove(n);
    for (int i = 1 , now = n ; i <= n ; i ++) {
        while(now >= 1) {
            if (check(now)) remove(-- now);
            else break;
        }
        cout << now << ' ';
        add(b[i]);
    }
	return 0;
}
posted @ 2024-04-07 00:32  Fracture_Dream  阅读(9)  评论(0)    收藏  举报  来源