CSU 1913
题意:中文题
分析:首先由于只关心最后的结果,所以op2相当于简单的撤销之前的p个op1的操作,用一个栈模拟一下即可,这样我们就能得到一个只有 op1的操作序列。
再对于保存下的只有 op1 的操作序列,对答案二分,设为 ans 。将数组中大于 ans 的数设为1,小于等于 ans 的数设为0,存到线段树中,然后每个操作 [l,r] 就变成了如下步骤
1.查询[l,r]的和,记为tot(可以理解为这个区间中有多少的数比当前的二分值还大)
2.将区间右边的tot/2个数改成1,左边的(tot+1)/2个数改成1,中间的(r-l+1)-tot个数改成0(如果tot为奇数那么左边有tot/2+1个数改成1)
最后再看当前的k位置上的数,如果这个值是0,说明答案小于等于ans,否则答案大于ans。
直到二分结束后能求出答案。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#include <math.h>
#include <vector>
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int inf=(int) 0x3f3f3f3f;
const int maxn=(int) 5e4+10;
int sum[maxn<<2];
int setv[maxn<<2];//lazy标记
int a[maxn];
int n,m,k;
int opL[maxn],opR[maxn];
int opcnt;
void push_up(int rt,int len){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
if (setv[rt]>=1){
sum[rt]=setv[rt]*len;
}
}
void push_down(int rt,int len){
if (setv[rt]==0)return;
int ls=rt<<1,rs=rt<<1|1;
//若访问到的节点有懒惰标记,将其移到子节点,清除其标记
if (sum[rt]>0){
sum[ls]=len-(len/2);
sum[rs]=len/2;
}else {
sum[ls]=sum[rs]=0;
}
setv[ls]=setv[rs]=1;
setv[rt]=0;
}
void bulid(int rt,int l,int r,int val){//建树
setv[rt]=0;
if (l==r){
sum[rt]=a[l]<=val?0:1;
return ;
}
int mid=(l+r)>>1;
bulid (rt*2,l,mid,val);
bulid (rt*2+1,mid+1,r,val);
push_up(rt,r-l+1);
}
void update(int rt,int l,int r,int ul,int ur,int val){//将区间(ul,ur)的值改为val
if (ul<=l&&ur>=r){
setv[rt]=1;
sum[rt]=val*(r-l+1);
}//如果区间包括在里面就直接标记
else{
push_down(rt,r-l+1);//若访问到的节点有懒惰标记,将其移到子节点,清除其标记
int mid=(r+l)>>1;
if (ul<=mid)update(rt*2,l,mid,ul,ur,val);
if (ur>mid)update(rt*2+1,mid+1,r,ul,ur,val);
push_up(rt,r-l+1);
}
}
int query(int rt,int l,int r,int ql,int qr){//查询区间(ql,qr)的和sum
if (ql<=l&&r<=qr){
return sum[rt];
}
push_down(rt,r-l+1);
int mid=(l+r)>>1;
if (qr<=mid)return query(rt*2,l,mid,ql,qr);
else if (ql>mid)return query(rt*2+1,mid+1,r,ql,qr);
else return query(rt*2,l,mid,ql,qr)+query(rt*2+1,mid+1,r,ql,qr);
}
void print(int l,int r,int rt){
if(l==r){
printf("%d ",sum[rt]);
return;
}
push_down(rt,r-l+1);
int mid=(l+r)>>1;
print(lson);
print(rson);
}
bool solve(int val){
bulid(1,1,n,val);//建树
// printf("al = %d : \n",val);
// print(1,n,1);
// printf (" first\n");
for (int i=0;i<opcnt;i++){//更新
int ql=opL[i],qr=opR[i];
int tot=query(1,1,n,ql,qr);//得到区间的和
int L=(tot+1)/2;//左边要修改为1的数目
int R=tot-L;//右边要修改为1 的数目
int ul=opL[i],ur=opL[i]+L-1;
if (ul<=ur)//修改左边
update(1,1,n,ul,ur,1);
ul=opR[i]-R+1,ur=opR[i];
if (ul<=ur)//修改右边
update(1,1,n,ul,ur,1);
ul=opL[i]+L,ur=opR[i]-R;
if (ul<=ur)//修改中间
update(1,1,n,ul,ur,0);
// print(1,n,1);
// printf ("----------i==%d\n",i);
}
// print(1,n,1);
// printf (" second\n");
int ql,qr;
ql=qr=k;
return query(1,1,n,ql,qr)==0;
}
int main()
{
while (scanf ("%d%d%d",&n,&m,&k)!=EOF){
int hi=0,lo=inf;
for (int i=1;i<=n;i++){
scanf ("%d",&a[i]);
hi=max(hi,a[i]);
lo=min(lo,a[i]);
}
opcnt=0;
int tmpop,tmpL,tmpR,tmp;
for (int i=0;i<m;i++){//求实际上需要操作的数目并保存起来
scanf ("%d",&tmpop);
if (tmpop==1){
scanf ("%d%d",&opL[opcnt],&opR[opcnt]);
opcnt++;
}else {
scanf ("%d",&tmp);
opcnt-=tmp;
if (opcnt<0)opcnt=0;
}
}
int ans;
// for (int i=0;i<opcnt;i++){
// printf ("%d %d\n",opL[i],opR[i]);
// }
//二分答案
while (lo<=hi){
int mid=(lo+hi)>>1;
if (solve(mid)){
ans=mid;
hi=mid-1;
}else {
lo=mid+1;
}
}
printf ("%d\n",ans);
}
return 0;
}