目录
一. 问题描述
Problem Description
给出一行n个数字,给出m个操作,每个操作指定一个区间 [ l , r ] , 若 l <=r 则将区间内升序,否则降序。求m次操作以后,整个区间的中值是多少。
二. 题解代码
考虑暴力求解的话,其复杂度为m*nlog(n)会超时,所以要优化降低复杂度。因为n个数字都有可能成为中值,所以可以考虑二分,然后又是关于区间修改操作,可以考虑线段树。
(1)二分答案为i,将大于 i 的设为 1 , 小于 i 的设为0。这样的线段树其实统计的是,每个区间内大于等于中值的数字的个数。
(2)线段树模拟m次操作,区间修改(实质上是修改个数),区间查询,若中值为1,则答案可能更大,否则可能更小。
算法最终复杂度为 m * (log(n))^2 ,其代码实现如下:
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000000 + 7;
const int maxm = 100000 + 7;
int n,m,sum[maxn<<2],sign[maxn<<2],ql[maxm],qr[maxm],num[maxn];
void BuildTree(int L,int R,int root,int p){//建树
sign[root] = 0;
if(L==R){
sum[root] = ((num[L] >= p)?1:0);
return;
}
int mid = (L + R)>>1;
BuildTree(L,mid,root<<1,p);
BuildTree(mid+1,R,root<<1|1,p);
sum[root] = sum[root<<1] + sum[root<<1|1];
}
void pushDown(int l,int r,int root){//下推标记
sign[root<<1] = sign[root<<1|1] = sign[root];
int mid = (l + r)>>1;
if(sign[root]==1){
sum[root<<1|1] = min(r - mid,sum[root]);
sum[root<<1] = sum[root] - sum[root<<1|1];
}
else if(sign[root]==2){
sum[root<<1] = min(mid - l + 1,sum[root]);
sum[root<<1|1] = sum[root] - sum[root<<1];
}
sign[root] = 0;
}
int QuerySum(int L,int R,int l,int r,int root){//查询区间和
if(L<=l&&R>=r){
return sum[root];
}
if(sign[root])pushDown(l,r,root);//先下推标记
int mid = (l + r)>>1;
int ans = 0;
if(L<=mid)ans+=QuerySum(L,R,l,mid,root<<1);
if(R>mid)ans+=QuerySum(L,R,mid+1,r,root<<1|1);
return ans;
}
int QueryMid(int pos,int l,int r,int root){//查询中值
if(l==r)return sum[root];
if(sign[root])pushDown(l,r,root);
int mid = (l + r)>>1;
if(pos<=mid)return QueryMid(pos,l,mid,root<<1);
else return QueryMid(pos,mid+1,r,root<<1|1);
}
void Update(int L,int R,int l,int r,int flag,int root,int res){//区间更新
if(L<=l&&R>=r){
sum[root] = res;
sign[root] = flag;//标记
return;
}
if(sign[root])pushDown(l,r,root);
int mid = (l + r)>>1;
if(R<=mid)Update(L,R,l,mid,flag,root<<1,res);//res不变
else if(L>mid)Update(L,R,mid+1,r,flag,root<<1|1,res);//res不变
else{//区间被分隔开,分别统计两侧的和
int lsum,rsum;
if(flag==1){//升序
rsum = min(R - mid,res);//先统计右侧,(填满,未填满)
lsum = res - rsum;//左侧是剩下的
}
else if(flag==2){//降序先统计左侧
lsum = min(mid - L + 1,res);
rsum = res - lsum;
}
Update(L,mid,l,mid,flag,root<<1,lsum);//更新左右区间,注意这里L,mid不是L,R了,因为后面还要统计不能用R - mid2,要用mid - mid2
Update(mid+1,R,mid+1,r,flag,root<<1|1,rsum);
}
sum[root] = sum[root<<1] + sum[root<<1|1];
}
int judge(int k){//判断当前中值是否可行
BuildTree(1,n,1,k);//对于当前中值建树
for(int i = 0;i<m;i++){
int res = QuerySum(min(ql[i],qr[i]),max(ql[i],qr[i]),1,n,1);//统计每个操作区间内大于中值的数字的数目
if(ql[i]<=qr[i])Update(ql[i],qr[i],1,n,1,1,res);//升序修改区间,1全放在区间右边
else Update(qr[i],ql[i],1,n,2,1,res);//降序修改区间,1全放在左边
}
return QueryMid((1+n)>>1,1,n,1);//查询区间中值
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i<=n;i++){
scanf("%d",&num[i]);
}
for(int i = 0;i<m;i++){
scanf("%d%d",&ql[i],&qr[i]);//离线操作
}
int l = 1,r = n;
int ans;
while(l<=r){
int mid = (l + r)>>1;
if(judge(mid)){
ans = mid;
l = mid+1;
}
else r = mid-1;
}
printf("%d\n",ans);
return 0;
}