2.2-2.3(2023)&2.4章节检测
2.2 线段树、主席树、树套树
2.3 平衡树(FHQ Treap/Splay)、替罪羊树
P2042 [NOI2005] 维护数列
坑点:
- 空间问题,要把删除了的节点循环利用
- 修改操作的值可能为0
- 时间问题,insert时直接把数列build(l,r),然后合并
- 最大子段和,
改了好几遍,每次以为改好了,结果后面又发现错误了,不能为空 - 修改操作,会让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]文本编辑器

这题太坑了,
“第四条输入文件没错”居然是错误的!!!
首先要读入字符'\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],有两种可能:
- \(a_i<=mx+1,可以表示的数的范围变为[1,mx+a_i]\)
- \(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;\)

浙公网安备 33010602011771号