洛谷P2839 [国家集训队]middle(二分 + 主席树 + 区间合并)

洛谷P2839 [国家集训队]middle(二分 + 主席树)

题意:

一个长度为 n n n 的序列 a a a,设其排过序之后为 b b b,其中位数定义为 b n / 2 b_{n/2} bn/2 ,其中 a , b a,b a,b 0 0 0 开始标号,除法取下整。给你一个长度为 n n n 的序列 s s s
回答 Q Q Q 个这样的询问: s s s 的左端点在 [ a , b ] [a,b] [a,b] 之间,右端点在 [ c , d ] [c,d] [c,d] 之间的子区间中,最大的中位数。
其中 a < b < c < d a<b<c<d a<b<c<d
位置也从 0 开始标号

思路:

将初始数组从小到大排序排好后,二分中位数的下标x ,判断这个位置的元素是否符合:贪心的想,在 x在[a,d]比a[x]小的数全部变成-1,大的数全变成1,再求和:在[a,b]区间求一个从右端点b开始往左的最大的连续区间和 + [b+1,c-1]和 + 在[c,d]区间求一个从左端点c开始往右的最大的连续区间和。
root[i]表示,比a数组中第i大数小的数已经全部赋值成-1,大的数全部赋值成1。root[i]的i满足连续性,可以二分。
用主席树,先把所有点赋值成1,再按权值从小到大,在相应位置(a数组的初始位置)-1。每次暴力logn的去修改树。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
const int INF=0x3f3f3f3f;
const int maxn=1e5+6;
int n,m,tot,a[maxn],id[maxn],root[maxn],p[6],pre;
struct NODE{
	int sum,ml,mr,l,r;
	void init(){
		sum=0,ml=mr=-INF;
	}
}tr[maxn*400],ANS;
bool cmp(int x,int y){
	return a[x]<a[y];
}
void build(int &t,int l,int r){//动态开点 
	t=++tot;
	tr[t].ml=tr[t].mr=tr[t].sum=r-l+1;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(tr[t].l,l,mid);
	build(tr[t].r,mid+1,r);
}
NODE merge(NODE h,NODE x,NODE y){//
	NODE z;
	z.l=h.l,z.r=h.r;//初始下标 
	z.sum=x.sum+y.sum;//sum直接加 
	z.mr=max(y.mr,y.sum+x.mr);//右区间最大连续和 
	z.ml=max(x.ml,x.sum+y.ml);//左区间最大连续和 
	return z;
}
void update(int &t,int l,int r,int x){
	tr[++tot]=tr[t],t=tot;
	if(l==r){
		tr[t].ml=tr[t].mr=tr[t].sum=-1;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)update(tr[t].l,l,mid,x);
	else update(tr[t].r,mid+1,r,x);
	tr[t]=merge(tr[t],tr[tr[t].l],tr[tr[t].r]);
}
void query(int t,int l,int r,int x,int y){
	if(x<=l&&r<=y){
		ANS=merge(ANS,ANS,tr[t]);//将每次查询的结果合并 
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)query(tr[t].l,l,mid,x,y);
	if(y>mid)query(tr[t].r,mid+1,r,x,y);
}
bool check(int pos){
	int he=0;
	if(p[2]+1<=p[3]-1){
		ANS.init();//记得初始化 
		query(root[pos],1,n,p[2]+1,p[3]-1);
		he+=ANS.sum;
	}
	ANS.init();
	query(root[pos],1,n,p[1],p[2]);
	he+=ANS.mr;
	ANS.init();
	query(root[pos],1,n,p[3],p[4]);
	he+=ANS.ml;
	return he>=0;
}
int solve(){
	for(int i=1;i<=4;i++)
	    p[i]=(p[i]+pre)%n+1;//下标从0开始,要+1 
	sort(p+1,p+1+4);
	int l=1,r=n,ans=0;//在id数组下标进行二分 
	while(l<=r){
		int mid=(l+r)>>1;
		if(check(mid))ans=mid,l=mid+1;
		else r=mid-1;
	}
	pre=a[id[ans]];
	return pre;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		id[i]=i;
	}
	sort(id+1,id+1+n,cmp);//离散化,将id数组按照 a的大小排序,方便处理 
	build(root[1],1,n);
	for(int i=2;i<=n;i++){
		root[i]=root[i-1];
		update(root[i],1,n,id[i-1]);
	}
	scanf("%d",&m);
	while(m--){
		scanf("%d%d%d%d",&p[1],&p[2],&p[3],&p[4]);
		printf("%d\n",solve());
	}
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
后缀自动机被广泛应用于OI竞赛中,特别是在字符串相关的问题中。它是一种高效的数据结构,能够有效地解决各种字符串匹配、模式匹配和计数等问题。 首先,后缀自动机可以用于解决最长公共子串和最长公共子序列等问题。对于给定的两个字符串,可以将其加入到后缀自动机中,并通过动态规划的方式求解最长公共子串或子序列的长度。 其次,后缀自动机还可以用于解决多次询问下的子串出现次数问题。通过构建全局后缀自动机,可以在O(n)的时间复杂度内预处理字符串,并在O(m)的时间复杂度内得出任意子串的出现次数,其中n为字符串长度,m为询问总数。 另外,后缀自动机还可以用于解决包含多模式匹配的问题。通过将模式串加入到后缀自动机中,并预处理自动机的fail指针,可以在O(n)的时间复杂度内找到所有模式串在文本中的出现位置。这在处理大规模的文本匹配问题时非常有用。 此外,后缀自动机还可以进行字符串的字典序统计。通过在构建自动机时记录每个节点的信息,可以在O(n)的时间复杂度内得到字符串的字典序第k小/大的子串。 总之,后缀自动机在OI竞赛中有着广泛的应用,能够解决各种字符串相关的问题。通过巧妙地构建自动机,并充分利用其性质,可以实现高效的字符串算法,为解决复杂的字符串问题提供了有力的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值