LGP2824【[HEOI2016/TJOI2016]排序】

一道神题ORZ,思路真的很妙啊。
### 正文部分:
题意:

给一个序列,可以对某一个区间升序和降序排序,问你最后数列中第$Q$个数是什么?
乍一看貌似毫无思路,于是我们考虑一个更简单的问题:
如果对$1$个$01$序列执行上面的操作,是不是就可以变得简单一点?
设某段区间$[l,r]$里总共有$cnt$个1
那么降序排就是把$l\sim l+cnt - 1$修改为$1$,把$l+cnt \sim r$修改为$0$
升序排则是把$r-cnt+1\sim r$修改为$1$,$l\sim r-cnt$修改为$0$
其实一个$01$序列有多少个$1$就是这个序列的和。
于是这样就变成了一道线段树问题:
区间修改,区间求和

于是我们回归原题,看是否能用一种“$01$序列”的方法维护原数列。
**答案是可以的。**
对于某一个数,我们把大于它的数设为$1$,小于它的数设为$0$,于是我们就得到了一个$01$序列。
把所有操作全部离线,跑一遍,于是我们就可以二分了。
为什么可以二分?
我们设想一下,如果这个$01$序列最后第$Q$位是$1$,说明最后的答案一定比这个数大,否则则一定比这个数小。

而题目又保证了一定为全排列,所以答案肯定只有一个。
于是这道题就变成了一个二分+线段树问题,可以通过了。

```cpp
#include <bits/stdc++.h>
#define gc getchar
#define il inline
#define lson(x) (x << 1)
#define rson(x) (x << 1 | 1)
const int MAXN = 1e5 + 10;
const int MAXQ = MAXN;
using namespace std;
int n,m,i,j,k,ans,Q;
int a[MAXN];bool b[MAXN];
int tr[MAXN << 2],tag[MAXN << 2];
struct Questions {
int l,r;bool opt;
Questions() {l = r = opt = 0;}
Questions(int L,int R,int Opt) {
l = L;r = R;opt = Opt;
}
}query[MAXQ];
il int read() {
int res = 0;char c;bool sign = 0;
for(c = gc();!isdigit(c);c = gc()) sign |= c == ‘-‘;
for(;isdigit(c);c = gc()) res = (res << 1) + (res << 3) + (c ^ 48);
return sign ? -res : res;
}
il void pushup(int num) {
tr[num] = tr[lson(num)] + tr[rson(num)];
return;
}
il void init(int num,int l,int r,int val) {
tr[num] = (r - l + 1) * val;
tag[num] = val;
return;
}
il void pushdown(int num,int l,int r) {
if(~tag[num]) {
int mid = l + r >> 1;
init(lson(num),l,mid,tag[num]);
init(rson(num),mid + 1,r,tag[num]);
tag[num] = -1;
}
return;
}
void build(int l,int r,int num) {
if(l == r) {tr[num] = b[l];return;}
int mid = l + r >> 1;
build(l,mid,lson(num));
build(mid + 1,r,rson(num));
pushup(num);
return;
}
void modify(int ml,int mr,int l,int r,int num,int val) {
if(ml <= l && r <= mr) {
tr[num] = (r - l + 1) * val;
tag[num] = val;
return;
}
int mid = l + r >> 1;
pushdown(num,l,r);
if(ml <= mid) modify(ml,mr,l,mid,lson(num),val);
if(mid < mr) modify(ml,mr,mid + 1,r,rson(num),val);
pushup(num);return;
}
int Query(int ql,int qr,int l,int r,int num) {
if(ql <= l && r <= qr) return tr[num];
pushdown(num,l,r);
int mid = l + r >> 1,res = 0;
if(ql <= mid) res += Query(ql,qr,l,mid,lson(num));
if(mid < qr) res += Query(ql,qr,mid + 1,r,rson(num));
return res;
}
il bool judge(int cknum) {
for(int i = 1;i <= n;i++) {
b[i] = a[i] >= cknum;
}
memset(tr,0,sizeof(tr));
memset(tag,-1,sizeof(tag));
build(1,n,1);
for(int i = 1;i <= m;i++) {
int opt = query[i].opt;
int l = query[i].l;
int r = query[i].r;
int cnt = Query(l,r,1,n,1);
if(!opt) {
modify(r - cnt + 1,r,1,n,1,1);
modify(l,r - cnt,1,n,1,0);
} else {
modify(l,l + cnt - 1,1,n,1,1);
modify(l + cnt,r,1,n,1,0);
}
}
return Query(Q,Q,1,n,1);
}
int main() {
n = read();m = read();
for(int i = 1;i <= n;i++) a[i] = read();
for(int i = 1;i <= m;i++) {
query[i].opt = read();
query[i].l = read();
query[i].r = read();
}
Q = read();
int l = 1,r = n;
while(l <= r) {
int mid = l + r >> 1;
if(judge(mid)) l = mid + 1,ans = mid;
else r = mid - 1;
}
printf("%d",ans);
return 0;
}
```

原文地址:https://www.cnblogs.com/Sai0511/p/10234532.html

时间: 2024-10-05 01:50:15

LGP2824【[HEOI2016/TJOI2016]排序】的相关文章

[HEOI2016/TJOI2016]排序 解题报告

[HEOI2016/TJOI2016]排序 题意 给出一个大小为 \(n\) 的排列, 对这个排列进行 \(m\) 次操作, 操作分为以下两种, 0 l r 表示将区间 \([l,r]\) 的数升序排序. 1 l r 表示将区间 \([l,r]\) 的数降序排序. 询问 \(m\) 次操作后下标为 \(q\) 的数字. 思路 不看题解打死也想不出来系列 考虑二分答案. 设当前二分的答案为 \(mid\), 把原排列中 大于等于 \(mid\) 的数标记为 \(1\), 小于 \(mid\) 的数

[HEOI2016/TJOI2016]排序

4552: [Tjoi2016&Heoi2016]排序 Time Limit: 60 Sec Memory Limit: 256 MB Submit: 2366 Solved: 1188 [Submit][Status][Discuss] Description 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题 ,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排 序分为两种:1:(0,l,

[HEOI2016&amp;TJOI2016] 排序(线段树)

4552: [Tjoi2016&Heoi2016]排序 Time Limit: 60 Sec  Memory Limit: 256 MBSubmit: 2703  Solved: 1386[Submit][Status][Discuss] Description 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题 ,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排 序分为两种:1:(0,l,

[Luogu2824] [HEOI2016/TJOI2016]排序

题目描述 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序最后询问第q位置上的数字. 输入输出格式 输入格式: 输入数据的第一行为两个整数n和m.n表示序列的长度,m表示局部排序的次数.1 <= n, m <= 10^5第二

【线段树合并】【P2824】 [HEOI2016/TJOI2016]排序

Description 给定一个长度为 \(n\) 的排列,有 \(m\) 次操作,每次选取一段局部进行升序或降序排序,问你一波操作后某个位置上的数字是几 Hint \(1~\leq~n,~m~\leq~10^5\) Solution 有两种做法,一种在线一种离线,这里把在线部分讲得更清楚点吧-- 考虑离线算法,我们二分该位置上的答案,将大于该数的元素置为 \(1\),小于该数的元素置为 \(0\),然后模拟所有的排序并检验.由于使用线段树对 \(0/1\) 序列多次局部排序可以做到 \(O(m

luogu_P2824 [HEOI2016/TJOI2016]排序

https://www.luogu.org/problem/P2824 题目描述 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排序分为两种:1:(0,l,r)表示将区间[l,r]的数字升序排序2:(1,l,r)表示将区间[l,r]的数字降序排序最后询问第q位置上的数字. 输入格式 输入数据的第一行为两个整数n和m.n表示序列的长度,m表示

Luogu P2824 [HEOI2016/TJOI2016]排序

Link 先二分答案,这样所有的数字就都变成了\(0,1\). 那么区间排序就相当于区间求和再区间覆盖了. #include<bits/stdc++.h> using namespace std; #define N 100007 int read(){int x=0,c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))x=x*10+c-48,c=getchar();return x;} int a[N],sum[N<&l

[LuoguP4094] [HEOI2016] [TJOI2016]字符串(二分答案+后缀数组+ST表+主席树)

[LuoguP4094] [HEOI2016] [TJOI2016]字符串(二分答案+后缀数组+ST表+主席树) 题面 给出一个长度为\(n\)的字符串\(s\),以及\(m\)组询问.每个询问是一个四元组\((a,b,c,d)\),问\(s[a,b]\)的所有子串和字符串\(s[c,d]\)的最长公共前缀长度的最大值. \(n,m \leq 10^5\) 分析 显然答案有单调性.首先我们二分答案\(mid\),考虑如何判定. 如果mid这个答案可行,那么一定存在一个后缀x,它的开头在\([a,

bzoj4552【TJOI2016&amp;HEOI2016】排序

4552: [Tjoi2016&Heoi2016]排序 Time Limit: 60 Sec  Memory Limit: 256 MB Submit: 291  Solved: 171 [Submit][Status][Discuss] Description 在2016年,佳媛姐姐喜欢上了数字序列.因而他经常研究关于序列的一些奇奇怪怪的问题,现在他在研究一个难题 ,需要你来帮助他.这个难题是这样子的:给出一个1到n的全排列,现在对这个全排列序列进行m次局部排序,排 序分为两种:1:(0,l,