首页 > 其他分享 >P2824 排序(二分答案加线段树)

P2824 排序(二分答案加线段树)

时间:2023-08-26 16:25:43浏览次数:46  
标签:二分 le P2824 线段 tr mid wz tag ll

传送门

很巧妙的一个题

直接排序肯定会\(T\)飞

我们发现问题只有一个:第\(q\)个位置上的数字

不难想到从这里入手,二分答案,考虑第\(q\)个位置上的数字是什么,不妨设他为\(x\)

然后把大于等于\(x\)的数变成\(1\),小于\(x\)的数变为\(0\),把他转换为一个\(01\)排序问题:

对于区间\([l,r]\)的排序,我们找到\([l,r]\)中有多少个\(1\),记为\(cnt_1\),同理记\(cnt_0\)为区间中\(0\)的个数,若其为升序,则令\([l,cnt_0+l-1]\)变为\(0\),\([r-cnt_1+1,r]\)变为\(1\),降序同理,令\([l,cnt_1+l-1]\)变为\(1\),\([r-cnt_0+1,r]\)变为\(0\)。

这样就变成了线段树基操:区间修改,区间查询

时间复杂度:

二分答案\(O(\log n)\),一次修改\(O(\log n)\),要修改\(m\)次\(O(m)\),再加上每次二分答案前初始化\(O(n\log n)\)

总共就是\(O((n+m)\log_2^2 n)\),可以接受

上代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=1e5+50;
ll n,m,q,a[N],b[N];
ll op[N],l[N],r[N];
ll tr[N*16],tag[N*16],cnt0,cnt1;
void BT(ll wz,ll l,ll r)
{
	if(l==r)
	{
		tr[wz]=b[l];
		tag[wz]=-1;
		return ;
	}
	ll mid=(l+r)/2;
	BT(wz*2,l,mid);
	BT(wz*2+1,mid+1,r);
	tr[wz]=tr[wz*2]+tr[wz*2+1];
	tag[wz]=-1;
}
void pushdown(ll wz,ll l,ll r)
{
	ll mid=(l+r)/2;
	tr[wz*2]=(mid-l+1)*tag[wz];
	tr[wz*2+1]=(r-mid)*tag[wz];
	tag[wz*2]=tag[wz];
	tag[wz*2+1]=tag[wz];
	tag[wz]=-1;
}
ll query(ll wz,ll l,ll r,ll le,ll ri)
{
	if(le<=l&&ri>=r) return tr[wz];
	if(tag[wz]!=-1) pushdown(wz,l,r);
	ll mid=(l+r)/2,re=0;
	if(le<=mid) re+=query(wz*2,l,mid,le,ri);
	if(ri>mid) re+=query(wz*2+1,mid+1,r,le,ri);
	return re;
}
void gai(ll wz,ll l,ll r,ll le,ll ri,ll x)
{
	if(le<=l&&ri>=r)
	{
		tr[wz]=(r-l+1)*x;
		tag[wz]=x;
		return ;
	}
	if(tag[wz]!=-1) pushdown(wz,l,r);
	ll mid=(l+r)/2;
	if(le<=mid) gai(wz*2,l,mid,le,ri,x);
	if(ri>mid) gai(wz*2+1,mid+1,r,le,ri,x);
	tr[wz]=tr[wz*2]+tr[wz*2+1];
} 
bool check(ll x)
{
	for(ll i=1;i<=n;i++)
	if(a[i]<x) b[i]=0;
	else b[i]=1;
	BT(1,1,n);
	for(ll i=1;i<=m;i++)
	{
		cnt1=query(1,1,n,l[i],r[i]);
		cnt0=r[i]-l[i]+1-cnt1;
		if(op[i]==0)
		{
			if(cnt0!=0) gai(1,1,n,l[i],l[i]+cnt0-1,0);
			if(cnt1!=0) gai(1,1,n,r[i]-cnt1+1,r[i],1);
		}
		if(op[i]==1)
		{
			if(cnt1!=0) gai(1,1,n,l[i],l[i]+cnt1-1,1);
			if(cnt0!=0) gai(1,1,n,r[i]-cnt0+1,r[i],0);
		}
	}
	ll re=query(1,1,n,q,q);
	if(re==1) return true;
	return false;
}
int main()
{
	scanf("%lld %lld",&n,&m);
	for(ll i=1;i<=n;i++)
	scanf("%lld",&a[i]);
	for(ll i=1;i<=m;i++)
	scanf("%lld %lld %lld",&op[i],&l[i],&r[i]);
	scanf("%lld",&q);
	ll l=1,r=n,mid;
	while(l<r)
	{
		mid=(l+r+1)/2;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	printf("%lld\n",l);
	return 0;
}

标签:二分,le,P2824,线段,tr,mid,wz,tag,ll
From: https://www.cnblogs.com/pengchujie/p/17658952.html

相关文章

  • 【算法-二分查找】实现过程、C++代码示例以及实际应用
    二分查找简介:也称为折半查找,是一个在已排序数组中查找特定元素的搜索算法。它的工作原理是将有序数组分成两半,然后检查目标值是在左半部分还是右半部分,然后在所选择的那部分中继续查找。这一过程将不断地重复,直到找到目标值或确定目标值不在数组中。实现过程:1.初始化两个指针:lo......
  • 线段树+动态开点权值线段树+主席树学习笔记
    线段树一般用于维护符合结合律的信息。可以用于求区间最大值区间和区间最小值最大子段和甚至于最大负数最小正数之类的信息。事实上线段树只有你想不到,很少有做不到的,算是相当常用的数据结构。下面将结合个人理解和具体题目来讲一讲线段树。[https://www.luogu.com.cn/proble......
  • 【主席树】洛谷 P3834 可持久化线段树 2
    【主席树】洛谷P3834可持久化线段树2题目链接:https://www.luogu.com.cn/problem/P3834主席树是可持久化线段树的一种,也叫做可持久化权值线段树,主要可以用来O(logn)求静态区间的第k小数。总所周知,普通线段树每次修改会遍历logn个点,那么我们在每次修改时都把这logn个点复制一份......
  • 可持久化线段树标记永久化?可刺激化修道士表舅已经黑!
    关于可刺激化修道士表舅已经黑。因为傻逼lxd告诉我我的表舅已经黑写法是错误的,所以稀里糊涂的让他改成了他的那种写法。但是我的也是对的。比如区间加和区间查和,维护一个\(tag\),表示表舅的值。然后在区间加的时候,经过的区间的\(sum\)的值可以直接加,但是只有在if(x<=l&......
  • 算法 -- 二分查找
    力扣题目链接给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回-1。示例1:输入:nums=[-1,0,3,5,9,12],target=9输出:4解释:9出现在nums中并且下标为4示例2:输入:nums=[-1......
  • 线段树
    线段树vs树状数组代码长度:树状数组段可扩展性:线段树强,二树状数组仅局限于和的处理思维难度:线段树简单比如区查区改树状数组还要打开多项式搞延迟标记:为了处理当修改区间是\([1,n]\)时所有节点都要被修改一遍的情况如果修改区间覆盖当前区间,那么这颗子树之内所有节点......
  • 代码随想录第一天|704.二分查找、27.移除元素
    二分查找对数组的要求有两点:有序无重复元素,若有重复元素则返回的元素下标不唯一边界条件是while(left<=right)代码其实是很好理解的点击查看代码classSolution{public:intsearch(vector<int>&nums,inttarget){intlength=nums.size();......
  • 【230823-3】▲ABC中,∠ABC=90°,AB=4,BC=3,点D在线段AC上,若角BDC=45°,则BD=?,Cos∠ABD=?
    ......
  • 数学——点到线段的最短距离
    点到线段最短距离的运算与点到直线的最短距离的运算二者之间存在一定的差别,即求点到线段最短距离时需要考虑参考点在沿线段方向的投影点是否在线段上,若在线段上才可采用点到直线距离公式。通俗的说,我们按照求点到直线的距离作垂线后,交点不一定在线段上。如图\(1\)所示。通常......
  • 6308: 和为给定数 二分
    描述 给出若干个整数,询问其中是否有一对数的和等于给定的数。 输入 第一行是整数n(0<n≤100,000),表示有n个整数。第二行是n个整数。整数的范围是在0到108之间。第三行是一个整数m(0≤m≤230),表示需要得到的和。  输出 若存在和为m的数对,输出两个整数,小......