题目描述
在 2016 年,佳媛姐姐喜欢上了数字序列。因而她经常研究关于序列的一些奇奇怪怪的问题,现在她在研究一个难题,需要你来帮助她。
这个难题是这样子的:给出一个 1 到 n 的排列,现在对这个排列序列进行 m 次局部排序,排序分为两种:
0 l r 表示将区间 [l,r] 的数字升序排序
1 l r 表示将区间 [l,r] 的数字降序排序
注意,这里是对下标在区间 [l,r] 内的数排序。
最后询问第 q 位置上的数字。
输入格式
输入数据的第一行为两个整数 n 和 m,n 表示序列的长度,m 表示局部排序的次数。
第二行为 n 个整数,表示 1 到 n 的一个排列。
接下来输入 m 行,每一行有三个整数 l,r,op
op 为 0 代表升序排序,op 为 1 代表降序排序, l,r 表示排序的区间。
最后输入一个整数 q,表示排序完之后询问的位置
输出格式
输出数据仅有一行,一个整数,表示按照顺序将全部的部分排序结束后第 q 位置上的数字。
输入输出样例
输入
6 3
1 6 2 5 3 4
0 1 4
1 3 6
0 2 4
3
输出
5
说明/提示
河北省选2016第一天第二题。
对于 30%30% 的数据,n,m≤1000
对于 100%100% 的数据,n,m≤105,1≤q≤n
题目分析
这道题需要我们进行m次排序,但排序是很慢的一种算法,直接排序是肯定会超时的。
那么我们来想想哪种序列是可以比较快速的进行排序的:01序列。
01序列可以在O(logn)的复杂度内进行排序。方法是:统计01序列中1的个数a,然后直接将序列后a个数赋为1,其余位置赋为0(通过线段树维护可以把复杂度降到logn)。
下一步我们要考虑如何把01序列应用到这道题上。
首先我们可以注意到,这道题只有一个查询。假设这次查询的答案为x,我们可以将序列中大于等于x的数变为1,而小于x的数变为0。这样序列就转化为了一个01序列了。
x的值我们可以枚举获得,但是要枚举获得n的话总时间复杂度会变为O(nmlogn),因此还要进行优化。我们可以发现答案x的值是具有单调性的,因此我们是可以二分x的值的。这样复杂度就变为O(mlog2n),符合要求。
代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#define LL long long
#define PII pair<int,int>
#define x first
#define y second
using namespace std;
const int N=1e5+5,INF=0x3f3f3f3f;
struct Sort{ //记录这m次排序操作
int op,l,r;
}q[N];
struct Node{ //线段树
int l,r; //sum记录01序列中1的个数
int sum,lazy; //lazy为懒标记:1代表将此段全部变为1,-1代表将此段全部变为0
}tr[N*4];
int n,m,k,a[N];
void pushup(int u)
{
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u)
{
if(tr[u].lazy)
{
tr[u<<1].lazy=tr[u<<1|1].lazy=tr[u].lazy;
if(tr[u].lazy==1){
tr[u<<1].sum=tr[u<<1].r-tr[u<<1].l+1;
tr[u<<1|1].sum=tr[u<<1|1].r-tr[u<<1|1].l+1;
}
else tr[u<<1].sum=tr[u<<1|1].sum=0;
tr[u].lazy=0;
}
}
void build(int u,int l,int r,int x)
{
if(l==r) tr[u]={l,r,a[l]>=x,0}; //序列中大于等于x的数变为1,小于x的数变为0
else {
tr[u]={l,r};
int mid=l+r>>1;
build(u<<1,l,mid,x),build(u<<1|1,mid+1,r,x);
pushup(u);
}
}
int query(int u,int l,int r) //查询[l,r]中1的个数
{
if(l<=tr[u].l&&tr[u].r<=r) return tr[u].sum;
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
int sum=0;
if(mid>=l) sum=query(u<<1,l,r);
if(mid<r) sum+=query(u<<1|1,l,r);
return sum;
}
void update(int u,int l,int r,int c) //将[l,r]区间中的数全部变为c
{
if(l<=tr[u].l&&tr[u].r<=r)
{
tr[u].sum=c*(tr[u].r-tr[u].l+1);
tr[u].lazy=c?1:-1;
}
else {
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(mid>=l) update(u<<1,l,r,c);
if(mid<r) update(u<<1|1,l,r,c);
pushup(u);
}
}
bool queryPoint(int u,int x) //查询x位置上的数是否为1
{
if(tr[u].l==tr[u].r) return tr[u].sum;
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(x<=mid) return queryPoint(u<<1,x);
else return queryPoint(u<<1|1,x);
}
bool check(int mid) //检查此答案值是否合法
{
build(1,1,n,mid); //用此答案值建树
for(int i=0;i<m;i++)
{
int op=q[i].op,l=q[i].l,r=q[i].r; //对[l,r]区间进行排序
int cnt=query(1,l,r); //查询[l,r]中1的个数
if(cnt==0||cnt==r-l+1) continue; //如果区间中的数全部相同,那么不需要进行排序
if(op)
{
update(1,l,cnt+l-1,1);
update(1,cnt+l,r,0);
}
else
{
update(1,l,r-cnt,0);
update(1,r-cnt+1,r,1);
}
}
return queryPoint(1,k); //所有操作完成后查看k位置上的数是否为1
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=0;i<m;i++)
scanf("%d%d%d",&q[i].op,&q[i].l,&q[i].r);
scanf("%d",&k);
int l=1,r=n; //二分答案x
while(r>l)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid; //如果为真说明此解合法,看看也没有更大的解l=mid
else r=mid-1; //如果为假说明此解不合法,减小答案r=mid-1
}
printf("%d\n",l);
return 0;
}