主席树 [题解]

影魔

一个[1,n]的排列a

二元组(i,j)产生p1的贡献,满足x是(i,j)内\(a_x\)最大的位置,\(a_x<a_i且a_x<a_j\)

二元组(i,j)产生p2的贡献,满足x是(i,j)内\(a_x\)最大的位置,\(a_j<a_x<a_i或a_i<a_x<a_j\)

套路:对于三元组(i,j,k)且满足i< j< k,考虑枚举 j 计算贡献

做出单调栈,l[i],r[i]表示 i 管辖的左右端点

i 作为最大值时,(l[i]-1,r[i]+1)做出p1的贡献

\((l_i-1,[i+1,r_i])和([l_i,i-1],r_i+1)\)做出p2的贡献

问题转化成二维平面数点,主席树维护

代码

middle

首先将询问区间分成三部分[a,b-1],[b,c],[c+1,d],即为k1,k2,k3

k2是必须选的,k1选择一段后缀,k3选择一段前缀

套路:二分中位数x,所有>=x的数置为1,所有<x的数置为-1,check区间中位数是否为不小于x只需判断区间和是否>=0

在此题只需check k2的区间和 + k1的最大后缀和+k3的最大前缀和是否>=0

区间最大子段和+可持久化线段树

可持久化线段树下标为位置,对于每个中位数x记录一种版本

考虑变化,x->x+1时只需将等于x的位置1->-1

代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+11;
struct tree{int lc,rc,suf,pre,sum;}tre[80*N];
struct ans_{int sum,sp;};
int n,q,ans,tot,maxx;
int root[N];
int a[N],lsh[N];
vector<int> vct[N];
inline int max_(int x,int y){return x>y?x:y;}
inline int read()
{
	int s=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
	return s;
}
inline void get(int &x){x=(read()+ans)%n+1;return;}
void lsh_()
{
	sort(lsh+1,lsh+n+1);
	int x=unique(lsh+1,lsh+n+1)-lsh;
	for(int i=1;i<=n;++i) a[i]=lower_bound(lsh+1,lsh+x,a[i])-lsh;
	return;
}
void px(int &a,int &b,int &c,int &d)
{
	int st[4]={a,b,c,d};
	sort(st,st+4);
	a=st[0],b=st[1],c=st[2],d=st[3];
	return;
}
void update(int i)
{
	tre[i].suf=tre[tre[i].lc].suf+tre[tre[i].rc].sum;
	tre[i].suf=max_(tre[i].suf,tre[tre[i].rc].suf);

	tre[i].pre=tre[tre[i].lc].sum+tre[tre[i].rc].pre;
	tre[i].pre=max_(tre[i].pre,tre[tre[i].lc].pre);

	tre[i].sum=tre[tre[i].lc].sum+tre[tre[i].rc].sum;
	return;
}
void insert(int &i,int j,int l,int r,int x,int val)
{
	i=++tot,tre[i]=tre[j];
	tre[i].sum+=val;
	if(l==r){tre[i].suf=tre[i].pre=tre[i].sum;return;}
	int mid=(l+r)>>1;
	if(x<=mid) insert(tre[i].lc,tre[j].lc,l,mid,x,val);
	else insert(tre[i].rc,tre[j].rc,mid+1,r,x,val);
	update(i);
	return;
}
int query(int i,int l,int r,int x,int y)
{
	if(l>=x&&r<=y) return tre[i].sum;
	int mid=(l+r)>>1;
	int sum=0;
	if(x<=mid) sum=query(tre[i].lc,l,mid,x,y);
	if(y>mid) sum+=query(tre[i].rc,mid+1,r,x,y);
	return sum;
}
ans_ get_max(int i,int l,int r,int x,int y,bool jd)
{
	if(l>=x&&r<=y) return (ans_){tre[i].sum,jd?tre[i].pre:tre[i].suf};
	int mid=(l+r)>>1;
	ans_ ans1={0,0},ans2={0,0},ans;
	if(x<=mid) ans1=get_max(tre[i].lc,l,mid,x,y,jd);
	if(y>mid) ans2=get_max(tre[i].rc,mid+1,r,x,y,jd);
	ans.sum=ans1.sum+ans2.sum;
	ans.sp=jd?max_(ans1.sp,ans1.sum+ans2.sp):max_(ans2.sp,ans1.sp+ans2.sum);
	return ans;
}
int check(int x,int a,int b,int c,int d)
{
	int sum=0;
	sum=query(root[x],1,n,b,c);
	sum+=max_(0,get_max(root[x],1,n,c+1,d,1).sp);
	sum+=max_(0,get_max(root[x],1,n,a,b-1,0).sp);
	return sum;
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i) lsh[i]=a[i]=read();
	lsh_();
	for(int i=1;i<=n;++i) maxx=max_(maxx,a[i]),vct[a[i]].push_back(i);
	root[1]=root[0]=++tot;
	for(int i=1;i<=n;++i) insert(root[1],root[1],1,n,i,1);
	for(int i=2;i<=maxx;++i)
	{
		root[i]=root[i-1];
		for(int j=0;j<vct[i-1].size();++j) insert(root[i],root[i],1,n,vct[i-1][j],-2);
	}
	q=read();
	for(int l,r,mid,a,b,c,d,i=1;i<=q;++i)
	{
		get(a),get(b),get(c),get(d);
		px(a,b,c,d);
		l=1,r=maxx,ans=1;
		while(l<=r)
		{
			mid=(l+r)>>1;
			if(check(mid,a,b,c,d)>=0) l=mid+1,ans=mid;
			else r=mid-1;
		}
		printf("%d\n",ans=lsh[ans]);
	}
	return 0;
}

神秘数

首先考虑一个集合最小不能表示的数是多少

结论:设\(sum_i=\sum^{i-1}_{j=1}a_i\),答案就是第一个满足$sum_i-a_i+1 < 0 $的位置

证明:到 i 可以表示\([1,sum_i]\),此时加入\(a_i\)可以表示\((a_i,1+a_i...,sum_i+a_i)\),合并的条件是\(sum_i>=a_i-1\)

此时复杂度\(O(nm\log n)\)

不必一个个数往里加,当前能表示[1,pos],那么 < = pos+1 的数可一次性加入

具体来说,维护last,pos,表示上一次加的区间右端点为last-1,当前集合最大能表示到pos,

新的区间为[pos+1,val+pos],val为所有值域在[last,pos]内的数的和,val=0时答案即为pos+1

复杂度\(O(m\log^2n)\),左端点每次至少翻倍

代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+11;
struct tree{
	int lc,rc;
	int sum;
}tre[80*N];
int n,m,tot;
int root[N];
int sum[N],a[N];
inline int read()
{
	int s=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+(ch^48),ch=getchar();
	return s;
}
void insert(int &i,int j,int l,int r,int x)
{
	i=++tot;
	tre[i]=tre[j];
	tre[i].sum+=x;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(x<=mid) insert(tre[i].lc,tre[j].lc,l,mid,x);
	else insert(tre[i].rc,tre[j].rc,mid+1,r,x);
}
int query(int i,int j,int l,int r,int x,int y)
{
	if(l>=x&&r<=y) return tre[j].sum-tre[i].sum;
	int mid=(l+r)>>1;
	int sum=0;
	if(x<=mid) sum=query(tre[i].lc,tre[j].lc,l,mid,x,y);
	if(y>mid) sum+=query(tre[i].rc,tre[j].rc,mid+1,r,x,y);
	return sum;
}
int get_max(int i,int j,int l,int r)
{
	if(l==r) return l;
	int mid=(l+r)>>1;
	if(tre[tre[j].rc].sum-tre[tre[i].rc].sum) return get_max(tre[i].rc,tre[j].rc,mid+1,r);
	return get_max(tre[i].lc,tre[j].lc,l,mid);
}
int main()
{
	n=read();
	root[0]=++tot;
	int maxx=0;
	for(int i=1;i<=n;++i) a[i]=read(),maxx=maxx>a[i]?maxx:a[i];
	for(int i=1;i<=n;++i) insert(root[i],root[i-1],1,maxx,a[i]),sum[i]=sum[i-1]+a[i];
	m=read();
	for(int mx,pos,mxn,l,r,val,i=1;i<=m;++i)
	{
		l=read(),r=read();
		mx=0,pos=1;
		mxn=get_max(root[l-1],root[r],1,maxx);
		val=0;
		while(val=query(root[l-1],root[r],1,maxx,mx+1,pos))
		{
			mx=pos;
			pos+=val;
			if(pos>=mxn){pos=sum[r]-sum[l-1]+1;break;}
		}
		printf("%d\n",pos);
	}
	return 0;
}

在以上三题中,主席树的作用分别是二维平面数点,代替线段树求最大子段和,位置区间和值域区间双重限制下的权值和

posted @ 2021-12-15 09:59  sitiy  阅读(43)  评论(0编辑  收藏  举报