草原追击

草原追击 By 2023.9.1

题目描述

MT 受了重伤藏到了一个大草原中,大草原有一排 n n n 个草丛,每个草丛高度为 a i a_i ai,MT 刚开始藏到了其中第 p o s pos pos 个草丛中,草丛编号从 1 1 1 开始。

为了防止追杀 MT 的人找到他,MT 会使用 q q q 次间接传送魔法在 n n n 个草丛中来回跳跃,间接传送魔法效果如下:

  1. l , r l,r l,r 表示将 a l , a l + 1 , … , a r a_l,a_{l+1},…,a_r al,al+1,,ar 的草丛按照非递减排序。
  2. l , r l,r l,r 表示将 a l , a l + 1 , … , a r a_l,a_{l+1},…,a_r al,al+1,,ar 的草丛按照非递增排序。

MT 的魔法十分高超,他会跟着草丛一起传送,且每次排序都是稳定排序(稳定排序指相同的元素排序后不改变它们的原本的相对位置)。

然而你拥有鹰的眼睛,MT 每次使用间接传送魔法都会被你看的一清二楚,请输出 MT 每次传送后的新位置。

输入格式

第一行三个整数, n , q , p o s n,q,pos n,q,pos

第二行输入 n n n 个整数 a 1 , a 2 , … , a n a1,a2,…,an a1,a2,,an

接下来 q q q 行,每行三个整数 o p , l , r op,l,r op,l,r

1 ≤ p o s ≤ n , q , a i ≤ 1 0 5 1\le pos\le n, q, a_i\le 10^5 1posn,q,ai105

1 ≤ o p ≤ 2 1\le op\le 2 1op2

1 ≤ l ≤ r ≤ n 1\le l\le r\le n 1lrn

输出格式

输出 q q q 行,每行一个整数表示 MT 使用间接传送魔法后的位置。

样例

样例输入1
10 5 3
1 2 2 3 4 2 10 9 1 7
2 1 2
1 3 6
1 1 10
2 1 10
1 5 7
样例输出1
3
3
4
7
6
样例输入2
13 3 6
1 1 4 5 1 4 1 9 1 9 8 1 0
1 1 4
1 5 9
2 8 9
样例输出2
6
8
9

笨蒟蒻的个人题解

传送门

萌新场,一道好题。当时赛时笨蒟看到写的人好少,而且通过的人也好少,想必这一定是一道超级难题,然后笨蒟蒻赛时就没敢写这个题,菜死了,于是现在来补,就发现这是一道好题,以前从来没见过这种想法的……

题意:一堆草丛,给其中一个打标记,然后每次对某些草丛做非上升或非下降操作,问每次操作完标记的位置在哪?

暴力写法:每次操作,是非递增或非递减,那我们就也跟着进行排序操作。可是这样的时间复杂度 O ( q n l o g n ) O(qnlogn) O(qnlogn),远远超时,显然不可行。

So, how?

区间操作,就要想到线段树,本题就是用线段树来解决的。

线段树能排序?不行吧,笨蒟蒻一开始是真的想不明白……

看到一种神奇的解法,也瞬间让笨蒟蒻醍醐灌顶。

正片开始:

对于初始位置的值,假设 w [ p o s ] = 5 w[pos] = 5 w[pos]=5

那么对于小于 5 5 5 的数字,比如说 2 , 3 2, 3 2,3 其实都是一样的,排完序之后的,根本不需要在意其它数字是多少,只要在意那个 5 5 5 就好了,也就是说小于 5 5 5 的,我们可以一视同仁,它的价值和贡献都是一样的。

同理可得,对于大于 5 5 5 的,也可以一视同仁。

那还有就是等于 5 5 5 的,对于每一个等于 5 5 5 的位置 j j j,不管以后如何排序,他们的位置都是保持相对不变的,因此特殊处理一下。把 j < p o s j<pos j<pos 5 5 5 的位置、 j = = p o s j == pos j==pos 5 5 5 的位置和 j > p o s j > pos j>pos 5 5 5 的位置分别分类。

因此我们只需要管 w [ p o s ] w[pos] w[pos] 的数值就好了。

~~===>>>

对上面先总结一下:对 w [ p o s ] w[pos] w[pos] 一共分成 5 5 5 类。

  1. 小于 5 5 5 的,记为 0 0 0
  2. 等于 5 5 5 的, j < p o s j < pos j<pos 记为 1 1 1
  3. 等于 5 5 5 的, j = = p o s j == pos j==pos 记为 2 2 2
  4. 等于 5 5 5 的, j > p o s j > pos j>pos 记为 3 3 3
  5. 大于 5 5 5 的,记为 4 4 4

<<<===~~

Ok,划分完之后,就变成了一个由 { 0 , 1 , 2 , 3 , 4 } \lbrace 0,1, 2, 3, 4\rbrace {0,1,2,3,4} 组成的集合。当然,这样划分的好处就是为了方便下面线段树处理。

先建立起线段树,因此预处理后只有 5 5 5 个数,我们就可以开 c n t [ 5 ] cnt[5] cnt[5] 了, c n t [ ] cnt[] cnt[] 代表子区间里的集合元素数量。

接下来处理两个操作:

以操作 1 1 1 为例,要求非递减摆放,那么可以这样子,先查询出这一区间 [ l , r ] [l, r] [l,r] 的每个元素数量 d [ ] d[] d[],然后依此摆放,先摆 0 0 0 数量 d [ 0 ] d[0] d[0],即更新 [ l , l + d [ 0 ] − 1 ] [l, l + d[0] - 1] [l,l+d[0]1] 的值,因为比 w [ p o s ] w[pos] w[pos] 小肯定放前面,然后摆 1 1 1 数量 d [ 1 ] d[1] d[1],即更新 [ l + d [ 0 ] , l + d [ 0 ] + d [ 1 ] − 1 ] [l + d[0],l + d[0] + d[1] - 1] [l+d[0],l+d[0]+d[1]1] 的值,虽然和 w [ p o s ] w[pos] w[pos] 一样,但是稳定排序,位置相对不变。此时再摆 2 2 2,同时这时候记录 2 2 2 的位置,这个位置就是我们需要记录的答案,之后摆 3 3 3……。

然后操作二也是一样的。

(可你别说,笨蒟一开始搞操作二的时候又搞错了

是怎么一回事呢?

操作一,是正的,顺序是 0 − > 1 − > 2 − > 3 − > 4 0->1->2->3->4 0>1>2>3>4 对吧,操作二呢?

笨蒟蒻一开始以为,反过来嘛,就是 4 − > 3 − > 2 − > 1 − > 0 4->3->2->1->0 4>3>2>1>0,然后记录 2 2 2 的位置,hhh

发现始终和样例对不上,我一开始还以为样例有问题,是我太菜了啊~~

后面模拟了一遍,菜找出错误来,所以不能理所应当。。。

反过来应该是 4 − > 1 − > 2 − > 3 − > 0 4->1->2->3->0 4>1>2>3>0

因为稳定排序, 1 , 2 , 3 1,2,3 1,2,3 要放在一起……

反过来主要就是这个需要注意的,和上面操作一的分析一样。

接下来就是写答辩树了,要仔细点哦~

好好领会,献上笨蒟蒻写的 2h 代码(菜死力。。。

AC_Code

#include<iostream>
#include<algorithm>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f
using namespace std;
typedef pair<int, int>PII;

const int N = 1e5 + 10, M = N * 2, Mod = 1e9 + 7;

int n, q, pos;
int w[N], a[N];
struct Node
{
    int l, r, lz;
    int cnt[5];
} tr[N << 2];

inline void push_up(int u)
{
    for(int i = 0; i < 5; i++) tr[u].cnt[i] = tr[u << 1].cnt[i] + tr[u << 1 | 1].cnt[i];
}

inline void push_down(int u)
{
    Node& root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];

    if(root.lz != -1)
    {
        for(int i = 0; i < 5; i++) left.cnt[i] = 0;
        for(int i = 0; i < 5; i++) right.cnt[i] = 0;
        left.cnt[root.lz] = left.r - left.l + 1, left.lz = root.lz;
        right.cnt[root.lz] = right.r -right.l + 1, right.lz = root.lz;
        root.lz = -1;
    }
}

inline void build(int u, int l, int r)
{
    if(l == r) tr[u] = {l, r}, tr[u].cnt[w[l]]++, tr[u].lz = -1;
    else
    {
        tr[u] = {l, r}, tr[u].lz = -1;
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        push_up(u);
    }
}

inline void modify(int u, int l, int r, int d)
{
    if(l <= tr[u].l && r >= tr[u].r)
    {
        for(int i = 0; i < 5; i++) tr[u].cnt[i] = 0;
        tr[u].cnt[d] = tr[u].r - tr[u].l + 1;
        tr[u].lz = d;
    }
    else
    {
        push_down(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid) modify(u << 1, l, r, d);
        if(r > mid) modify(u << 1 | 1, l, r, d);
        push_up(u);
    }
}

inline array<int, 5> query(int u, int l, int r)
{
    if(l <= tr[u].l && r >= tr[u].r)
    {
        array<int, 5> res = {0, 0, 0, 0, 0};
        for(int i = 0; i < 5; i++) res[i] = tr[u].cnt[i];
        return res;
    }
    else
    {
        push_down(u);
        int mid = tr[u].l + tr[u].r >> 1;
        array<int, 5> res = {0, 0, 0, 0, 0};
        if(l <= mid)
        {
            array<int, 5> d = query(u << 1, l, r);
            for(int i = 0; i < 5; i++) res[i] += d[i];
        }
        if(r > mid)
        {
            array<int, 5> d = query(u << 1 | 1, l, r);
            for(int i = 0; i < 5; i++) res[i] += d[i];
        }
        return res;
    }
}

void solve()
{
    cin >> n >> q >> pos;
    for(int i = 1; i <= n; i++) cin >> a[i];

    for(int i = 1; i <= n; i++)
    {
        if(a[i] < a[pos]) w[i] = 0;
        else if(a[i] == a[pos])
        {
            if(i < pos) w[i] = 1;
            else if(i == pos) w[i] = 2;
            else w[i] = 3;
        }
        else w[i] = 4;
    }

    build(1, 1, n);

    while(q--)
    {
        int op, l, r;
        cin >> op >> l >> r;
        array<int, 5> d = query(1, l, r);
        if(op == 1)
        {
            if(d[0]) modify(1, l, l + d[0] - 1, 0), l += d[0];
            if(d[1]) modify(1, l, l + d[1] - 1, 1), l += d[1];
            if(d[2])
            {
                pos = l;
                modify(1, l, l + d[2] - 1, 2);
                l += d[2];
            }
            if(d[3]) modify(1, l, l + d[3] - 1, 3), l += d[3];
            if(d[4]) modify(1, l, l + d[4] - 1, 4), l += d[4];
        }
        else if(op == 2)
        {
            if(d[4]) modify(1, l, l + d[4] - 1, 4), l += d[4];
            if(d[1]) modify(1, l, l + d[1] - 1, 1), l += d[1];
            if(d[2])
            {
                pos = l;
                modify(1, l, l + d[2] - 1, 2);
                l += d[2];
            }
            if(d[3]) modify(1, l, l + d[3] - 1, 3), l += d[3];
            if(d[0]) modify(1, l, l + d[0] - 1, 0), l += d[0];
        }
        cout << pos << endl;
    }
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int T = 1;
//    cin >> T;
    while(T--)
        solve();
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值