「Luogu2824」[HEOI2016/TJOI2016]排序

problem


真的妙


题目描述

在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第二行为n个整数,表示1到n的一个全排列。接下来输入m行,每一行有三个整数op, l, r, op为0代表升序排序,op为1代表降序排序, l, r 表示排序的区间。最后输入一个整数q,q表示排序完之后询问的位置, 1 <= q <= n。1 <= n <= 10^5,1 <= m <= 10^5

输出格式:

输出数据仅有一行,一个整数,表示按照顺序将全部的部分排序结束后第q位置上的数字。

输入样例#1:

1
2
3
4
5
6
6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3

输出样例#1:

1
5

说明

河北省选2016第一天第二题。原题的时限为6s,但是洛谷上是1s,所以洛谷的数据中,对于30%的数据,有 n,m<=1000,对于100%的数据,有 n,m<=50000

Solution

O(nmlogn)O(nm\log n)
emmmmm

由于最后才有一个询问,考虑离线

我们可以二分最终qq位置上的值ansans是什么

然后把序列中大于等于ansans的值设为11,小于ansans的值设为00

于是每次排序操作相当于区间覆盖,可以用线段树实现

最后Check一下qq位置上是00还是11,进一步二分即可

时间复杂度O(mlog2n)O(m\log^2n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <cmath>
#define maxn 30005
#define maxm 30005
using namespace std;
typedef long long ll;
//1:>=mid 0:<mid

int n,m,Q;
int a[maxn];

struct querys
{
int op,l,r;
}q[maxm];

struct node
{
int cnt1,l,r,lazy;
}t[maxn*4];

void maintain(int num)
{
t[num].cnt1=t[num*2].cnt1+t[num*2+1].cnt1;
}

void pushdown(int num)
{
if(t[num].lazy==1)
t[num*2].cnt1=t[num*2].r-t[num*2].l+1,t[num*2+1].cnt1=t[num*2+1].r-t[num*2+1].l+1;
if(t[num].lazy==0)
t[num*2].cnt1=t[num*2+1].cnt1=0;
if(t[num].lazy!=2)
t[num*2].lazy=t[num*2+1].lazy=t[num].lazy;
t[num].lazy=2;
}

void Build(int l,int r,int num,int x)
{
t[num].l=l;
t[num].r=r;
t[num].lazy=2;
if(l==r)
{
t[num].cnt1=(a[l]>=x);
return;
}
int mid=(l+r)/2;
Build(l,mid,num*2,x);
Build(mid+1,r,num*2+1,x);
maintain(num);
}

void Change(int l,int r,int num,int c)
{
if(t[num].l>r || t[num].r<l)
return;
if(l<=t[num].l && r>=t[num].r)
{
t[num].cnt1=c*(t[num].r-t[num].l+1);
t[num].lazy=c;
return;
}
pushdown(num);
Change(l,r,num*2,c);
Change(l,r,num*2+1,c);
maintain(num);
}

int Query(int l,int r,int num)
{
if(t[num].l>r || t[num].r<l)
return 0;
if(l<=t[num].l && r>=t[num].r)
return t[num].cnt1;
pushdown(num);
return Query(l,r,num*2)+Query(l,r,num*2+1);
}

bool Check(int x)
{
Build(1,n,1,x);
for(register int i=1;i<=m;++i)
{
int k=Query(q[i].l,q[i].r,1);
if(q[i].op==0)
{
Change(q[i].l,q[i].r-k,1,0);
Change(q[i].r-k+1,q[i].r,1,1);
}
else
{
Change(q[i].l,q[i].l+k-1,1,1);
Change(q[i].l+k,q[i].r,1,0);
}
}
return Query(Q,Q,1);
}

int main()
{
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;++i)
scanf("%d",&a[i]);
for(register int i=1;i<=m;++i)
scanf("%d%d%d",&q[i].op,&q[i].l,&q[i].r);
scanf("%d",&Q);
register int l=1,r=n;
int ans;
while(l<=r)
{
int mid=(l+r)/2;
if(Check(mid))
ans=mid,l=mid+1;
else
r=mid-1;
}
printf("%d",ans);
return 0;
}

很妙的思想,通过二分确认临界值,将题目进行适当地转换从而简化算法复杂度

借鉴意义很大