题目链接 复杂的线段树
Time Limit: 4000 MS Memory Limit: 64 MB
Submit Status
秦怼长是一个爱钦定的人, 一年一度的集训队选拔又来了, 秦怼决定钦定一些人品特别好的人进入集训队.
秦怼先是给出了一个数组 A[1..n]A[1..n], 初始元素为 a1,a2,...,an 是 1∼n 的一个排列. 然后秦怼迅速地对数组施以了 mm 个操作. 每个操作针对一个区间 [l,r] (1≤l≤r≤n)[l,r] (1≤l≤r≤n), 秦怼将区间内的元素从小到大排序或者从大到小排序.
如果你能猜出最终数组中第 k 个元素 A[k] 的值, 就能钦定进队啦(误
输入第一行为两个整数 n(1≤n≤105)和 k(1≤k≤n), 表示数组的大小和需要猜的数的下标.
第二行有 n 个整数 a1,a2,...,an, 表示数组中初始的元素,且是 1∼n 的一个排列.
第三行一个整数 m(1≤m≤105), 表示操作的个数.
接下来有 m 行,每行三个整数 o l r(1≤l≤r≤n),表示一个操作. 如果 o=0,表示对区间 [l,r]从小到大排序. 如果 o=1,表示对区间 [l,r]从大到小排序.
输出包含一个整数的一行, 为最终数组中 A[k] 的值.
Sample Input | Sample Output |
---|---|
|
|
对于一个区间[ L,R ] 从大到小排,从小到大排,最后的到的数组问第k个是什么?
这种瞎几把骚搞的问题第一眼看上去,线段树根本没这种操作啊!
但是由于答案一定在1 - n 中, 考虑是否可以用二分找出结果;
假设答案是一个值mid,如果把数组a中所有>=mid的元素置为1,把所有< mid 都元素置为0,这样对于一个区间排序操作,如果降序则左边全1右边全0,如果升序,左边全0,右边全1,
这样做有利于的线段树,如果一个子区间刚好全部要被修改成1,只需要把他的父节点标记为1即可(lazy操作);
然后对于每次排序就变为了线段树的区间维护+lazy标记,线段树根节点维护的是区间的1的个数
排序完成之后,单点查询k,如果得到的是1,表明这结果有可能是对的,或者0的个数偏少;
为增加0的个数就要把二分区间的L=mid+1;
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1e5+7;
int n,k;
int a[maxn];
struct node
{
int o,l,r;
}op[maxn];
struct tree
{
int val;
int tag;
}b[maxn<<2];
void down(int l,int r,int rt)
{
if(b[rt].tag!=-1)
{
b[rt*2].tag=b[rt].tag;
b[rt*2+1].tag=b[rt].tag;//添加标记
int mid=(l+r)>>1;
b[rt*2].val=b[rt].tag*(mid-l+1);
b[rt*2+1].val=b[rt].tag*(r-mid);
b[rt].tag=-1;
}
}
void build(int l,int r,int rt,int val)
{
b[rt].tag=-1;
if(l==r)
{
b[rt].val=a[l]>=val?1:0;
return ;
}
int mid=(l+r)>>1;
build(l,mid,rt*2,val);
build(mid+1,r,rt*2+1,val);
b[rt].val=b[rt*2].val+b[rt*2+1].val;//记录区间1的个数
}
int query(int l,int r,int rt,int ll,int rr)
{
if(ll<=l&&r<=rr) return b[rt].val;
down(l,r,rt);
int mid=(l+r)>>1;
int ans=0;
if(ll<=mid) ans+=query(l,mid,rt*2,ll,rr);
if(rr>mid) ans+=query(mid+1,r,rt*2+1,ll,rr);
return ans;
}
void change(int l,int r,int rt,int ll,int rr,int val)//区间修改
{
if(ll<=l&&r<=rr)
{
b[rt].val=(r-l+1)*val;
b[rt].tag=val;
return ;
}
down(l,r,rt);
int mid=(l+r)>>1;
if(ll<=mid) change(l,mid,rt*2,ll,rr,val);
if(rr>mid) change(mid+1,r,rt*2+1,ll,rr,val);
b[rt].val=b[rt*2].val+b[rt*2+1].val;
}
int query1(int l,int r,int rt,int k)
{
if(l==r)
{
return b[rt].val;
}
down(l,r,rt);
int mid=(l+r)>>1;
if(k<=mid) return query1(l,mid,rt*2,k);
else return query1(mid+1,r,rt*2+1,k);
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int m;
scanf("%d",&m);
for(int i=0;i>1;
build(1,n,1,mid+1);
//for(int i=1;i
另一种二分的方式,这个好像是最基本的二分写法
while(l<=r)
{
int mid=(l+r)>>1;
build(1,n,1,mid);
//for(int i=1;i