2.2-2.3(2023)&2.4章节检测

2.2 线段树、主席树、树套树

2.3 平衡树(FHQ Treap/Splay)、替罪羊树

P2042 [NOI2005] 维护数列

坑点:

  1. 空间问题,要把删除了的节点循环利用
  2. 修改操作的值可能为0
  3. 时间问题,insert时直接把数列build(l,r),然后合并
  4. 最大子段和,改了好几遍,每次以为改好了,结果后面又发现错误了,不能为空
  5. 修改操作,会让max1和max2颠倒
点击查看代码
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}

const int N=5e5+5;
int tot,root,a,b,c,n,m,q,cnt,sq[N];
int ls[N],rs[N],dat[N],val[N],tag[N];
bool laz[N],falg[N];
int siz[N],sum[N],max0[N],max1[N],maxn[N],w[N];//0->l,1->r

int New(int k){
	
	int id=(cnt)?sq[cnt--]:++tot;
	val[id]=sum[id]=maxn[id]=k;
	ls[id]=rs[id]=tag[id]=laz[id]=falg[id]=0;
	max0[id]=max1[id]=max(0,k);
	siz[id]=1;dat[id]=rand();
	return id;
}

void updat(int p){
	if(!p)return ;
	siz[p]=siz[ls[p]]+siz[rs[p]]+1;
	sum[p]=sum[ls[p]]+sum[rs[p]]+val[p];
	max0[p]=max(0,max(max0[ls[p]],max0[rs[p]]+val[p]+sum[ls[p]]));
	max1[p]=max(0,max(max1[rs[p]],max1[ls[p]]+val[p]+sum[rs[p]]));
	maxn[p]=max(0,max1[ls[p]]+max0[rs[p]])+val[p];
	if(ls[p])maxn[p]=max(maxn[p],maxn[ls[p]]);
	if(rs[p])maxn[p]=max(maxn[p],maxn[rs[p]]);
}

void rever(int p){
	swap(ls[p],rs[p]);
	swap(max0[p],max1[p]);
	laz[p]^=1;
}

void cover(int p,int x){
	tag[p]=val[p]=x;
	sum[p]=siz[p]*x;
	maxn[p]=max(x,sum[p]);
	max0[p]=max1[p]=max(0,sum[p]);
	falg[p]=1;
}

void pushdown(int p){
	if(laz[p]){
		if(ls[p])rever(ls[p]);
		if(rs[p])rever(rs[p]);
		laz[p]=0;
	}
	if(falg[p]){
		if(ls[p])cover(ls[p],tag[p]);
		if(rs[p])cover(rs[p],tag[p]);
		tag[p]=falg[p]=0;
	}
}

void split(int p,int k,int &x,int &y){
	if(!p){
		x=y=0;return ;
	}
	pushdown(p);
	if(siz[ls[p]]<k){
		x=p;
		split(rs[p],k-siz[ls[p]]-1,rs[p],y);
	}
	else{
		y=p;split(ls[p],k,x,ls[p]);
	}
	updat(p);
}

int merge(int x,int y){
	if(!x||!y)return x+y;
	if(dat[x]>dat[y]){
		pushdown(x);
		rs[x]=merge(rs[x],y);
		updat(x);
		return x;
	}
	else
	{
		pushdown(y);
		ls[y]=merge(x,ls[y]);
		updat(y);
		return y;
	}
}

void cun(int p){
	if(!p)return ;
	sq[++cnt]=p;
	if(ls[p])cun(ls[p]);
	if(rs[p])cun(rs[p]);
}

void insert(int k,int x){
	split(root,k,a,b);
	root=merge(merge(a,New(x)),b);
}

int build(int l,int r){
	if(l==r)return New(w[l]);
	int mid=(l+r)>>1;
	int x=merge(build(l,mid),build(mid+1,r));
	return x;
}

int main(){
//	freopen("P2042_2.in","r",stdin);
//	freopen("1.out","w",stdout);
	n=read(),m=read();
	int x,y,z;string s;
	for(int i=1;i<=n;i++)w[i]=read();
	root=build(1,n);
	for(int i=1;i<=m;i++){
		cin>>s;
		if(s[0]=='I'){
			x=read(),y=read();
			for(int j=1;j<=y;j++)w[j]=read();
			split(root,x,a,b);
			root=merge(merge(a,build(1,y)),b);
		}
		else if(s[0]=='D'){
			x=read(),y=read();
			split(root,x-1,a,b);
			split(b,y,b,c);
			cun(b);
			root=merge(a,c);
		}
		else if(s[0]=='M'&&s[2]=='K'){
			x=read(),y=read(),z=read();
			split(root,x-1,a,b);
			split(b,y,b,c);
			cover(b,z);
			root=merge(merge(a,b),c);
		}
		else if(s[0]=='R'){
			x=read(),y=read();
			split(root,x-1,a,b);
			split(b,y,b,c);
			rever(b);
			root=merge(a,merge(b,c));
		}
		else if(s[0]=='G'){
			x=read(),y=read();
			split(root,x-1,a,b);
			split(b,y,b,c);
			printf("%d\n",sum[b]);
			root=merge(merge(a,b),c);
		}
		else{
			printf("%d\n",maxn[root]);
		}
	}
	return 0;
}

P4567 [AHOI2006]文本编辑器

image

这题太坑了,

“第四条输入文件没错”居然是错误的!!!

首先要读入字符'\n',且输出字符为'\n'时,只输出一个'\n',总而言之,言而总之,这题就是一个坑题。

想问下:当年安徽Oier的心情。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}

const int N=2e6+500000;
int tot,root,a,b,c,n;
int ls[N],rs[N],dat[N],siz[N];
bool laz[N];
char w[N],val[N];

int New(char x){
	int id=++tot;
	val[id]=x;
	dat[id]=rand();
	siz[id]=1;
	return id;
}

void updat(int p){
	if(!p)return ;
	siz[p]=siz[ls[p]]+siz[rs[p]]+1;
}

void rever(int p){
	swap(ls[p],rs[p]);
	laz[p]^=1;
}

void pushdown(int p){
	if(laz[p]){
		if(ls[p])rever(ls[p]);
		if(rs[p])rever(rs[p]);
		laz[p]=0;
	}
}

void split(int p,int k,int &x,int &y){
	if(!p){
		x=y=0;return ;
	}
	pushdown(p);
	if(siz[ls[p]]<k){
		x=p;
		split(rs[p],k-siz[ls[p]]-1,rs[p],y);
	}
	else{
		y=p;split(ls[p],k,x,ls[p]);
	}
	updat(p);
}

int merge(int x,int y){
	if(!x||!y)return x+y;
	if(dat[x]>dat[y]){
		pushdown(x);
		rs[x]=merge(rs[x],y);
		updat(x);
		return x;
	}
	else
	{
		pushdown(y);
		ls[y]=merge(x,ls[y]);
		updat(y);
		return y;
	}
}

int build(int l,int r){
	if(l==r)return New(w[l]);
	int mid=(l+r)>>1;
	int x=merge(build(l,mid),build(mid+1,r));
	return x;
}

int main(){
	n=read();
	int x,y,z;string s;
	int cnt=0;
	for(int i=1;i<=n;i++){
		cin>>s;
		if(s[0]=='I'){
			x=read();
			for(int j=1;j<=x;j++)w[j]=getchar();
			split(root,cnt,a,b);
			root=merge(merge(a,build(1,x)),b);
		}
		else if(s[0]=='M'){
			x=read();cnt=x;
		}
		else if(s[0]=='D'){
			x=read();
			split(root,cnt,a,b);
			split(b,x,b,c);
			root=merge(a,c);
		}
		else if(s[0]=='R'){
			x=read();
			split(root,cnt,a,b);
			split(b,x,b,c);
			rever(b);
			root=merge(merge(a,b),c);
		}
		else if(s[0]=='G'){
			split(root,cnt,a,b);
			split(b,1,b,c);
			if(val[b]!='\n')cout<<val[b]<<endl;
			else cout<<endl;
			root=merge(merge(a,b),c); 
		}
		else if(s[0]=='P'){
			cnt-=1;
		}
		else {
			cnt+=1;
		}
	}
	return 0;
}

2.4章节检测

P4587 [FJOI2016]神秘数

一道主席树的模板题

我们先考虑暴力的做法,对于区间[l,r],我们先把里面的a[i]进行升序排序。设当前可以表示的数为[1,mx],对于要插入的数a[i],有两种可能:

  1. \(a_i<=mx+1,可以表示的数的范围变为[1,mx+a_i]\)
  2. \(a_i>mx+1,无法表示mx+1,停止加入\)

然后对暴力做法进行优化。
设已经加入了的数字的范围为[1,mx],可以表示的数字的范围为[1,pos];
首先可以知道,新加入的数的值肯定在[1,pos+1]里面,而小于mx的数已经被加入了,所以我们要加入值在[mx+1,pos+1]范围内的数。把值在[mx+1,pos+1]中的数的和记为sum,如果sum=0,则无法加入,输出pos+1;如果sum!=0,mx=pos+1,pos+=sum;

点击查看代码
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x*f;
}

const int N=1e5+5;
int n,m,tot,a[N],maxn,rt[N];
struct ww{
	int val,ls,rs;
}tr[N<<5];

void insert(int &p,int pre,int l,int r,int x){
	p=++tot;
	tr[p]=tr[pre];tr[p].val+=x;
	if(l>=r)return ;
	int mid=(l+r)>>1;
	if(x<=mid){
		insert(tr[p].ls,tr[pre].ls,l,mid,x);
	}
	else{
		insert(tr[p].rs,tr[pre].rs,mid+1,r,x);
	}
}

int query(int p,int q,int l,int r,int x,int y){
	if(tr[p].val==tr[q].val)return 0;
	if(x<=l&&r<=y)return tr[q].val-tr[p].val;
	int mid=(l+r)>>1,res=0;
	if(x<=mid)res+=query(tr[p].ls,tr[q].ls,l,mid,x,y);
	if(y>mid)res+=query(tr[p].rs,tr[q].rs,mid+1,r,x,y);
	return res;
}

int main(){
	n=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
		maxn=max(maxn,a[i]);
	}
	for(int i=1;i<=n;i++){
		insert(rt[i],rt[i-1],1,maxn,a[i]);
	}
	m=read();int x,y;
	for(int i=1;i<=m;i++){
		x=read(),y=read();
		int mx=0,pos=0;
		while(1){
			int sum=query(rt[x-1],rt[y],1,maxn,mx+1,pos+1);
			if(!sum)break;
			mx=pos+1;pos+=sum;
		}
		printf("%d\n",pos+1);
	}
	return 0;
} 

P3215 [HNOI2011]括号修复 / [JSOI2011]括号序列

(=1,)=-1,维护区间和、前缀最大值、前缀最小值、后缀最大值、后缀最小值。

最终答案为\((prmax[b]+1)/2+(abs(sfmin[b])+1)/2\)

void updat(int p){
	siz[p]=siz[ls[p]]+siz[rs[p]]+1;
	sum[p]=sum[ls[p]]+sum[rs[p]]+val[p];
	prmax[p]=max(prmax[ls[p]],sum[ls[p]]+val[p]+prmax[rs[p]]);
	prmin[p]=min(prmin[ls[p]],sum[ls[p]]+val[p]+prmin[rs[p]]);
	sfmax[p]=max(sfmax[rs[p]],sum[rs[p]]+val[p]+sfmax[ls[p]]);
	sfmin[p]=min(sfmin[rs[p]],sum[rs[p]]+val[p]+sfmin[ls[p]]);
}

关于区间取反、覆盖、翻转(按优先级排序)

取反

void push(int p){
	if(!p)return ;
	val[p]*=-1;sum[p]*=-1;
	swap(prmin[p],prmax[p]);
	prmin[p]*=-1;prmax[p]*=-1;
	swap(sfmin[p],sfmax[p]);
	sfmin[p]*=-1;sfmax[p]*=-1;
	falg[p]^=1;tag[p]*=-1;
}

覆盖

void cover(int p,int x){
	if(!p)return ;
	val[p]=tag[p]=x;
	sum[p]=siz[p]*x;
	prmax[p]=prmin[p]=sfmax[p]=sfmin[p]=0;
	if(x==1)prmax[p]=sfmax[p]=sum[p];
	else prmin[p]=sfmin[p]=sum[p];
	
}

翻转

void rever(int p){
	if(!p)return ;
	swap(ls[p],rs[p]);
	swap(prmin[p],sfmin[p]);
	swap(prmax[p],sfmax[p]);
	laz[p]^=1;
}

需要注意的是\(New(x)\)函数中不能写成\(val[++tot]=sum[tot]=x\),这样是有歧义的,要分开写\(val[++tot]=x;sum[tot]=x;\)

posted @ 2023-02-03 20:36  两只风小鱼  阅读(49)  评论(0)    收藏  举报