草原追击 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 个草丛中来回跳跃,间接传送魔法效果如下:
- l , r l,r l,r 表示将 a l , a l + 1 , … , a r a_l,a_{l+1},…,a_r al,al+1,…,ar 的草丛按照非递减排序。
- 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 1≤pos≤n,q,ai≤105
1 ≤ o p ≤ 2 1\le op\le 2 1≤op≤2
1 ≤ l ≤ r ≤ n 1\le l\le r\le n 1≤l≤r≤n
输出格式
输出 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 类。
- 小于 5 5 5 的,记为 0 0 0。
- 等于 5 5 5 的, j < p o s j < pos j<pos 记为 1 1 1。
- 等于 5 5 5 的, j = = p o s j == pos j==pos 记为 2 2 2。
- 等于 5 5 5 的, j > p o s j > pos j>pos 记为 3 3 3。
- 大于 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;
}