一.线段树
线段树其实只是一种工具。我们不应该把它当做一道题的入手点,而只是当我们分析题目发现需要它时,才会去使用它。
基础操作:
1.区间加法,区间求和。
线段树lazytag入门操作
2.01覆盖翻转/01排序。
线段树维护区间覆盖标记和翻转标记,特别注意标记之间的相互影响。
例题:WOJ2832「TJOI / HEOI2016」排序
代码:
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=4e5+5;
int n,m,a[N],k,val[N];
inline int red(){
int data=0;int w=1; char ch=0;
ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
struct node{int op,l,r;}q[N];
namespace sgt{
#define lc (p<<1)
#define rc (p<<1|1)
#define len(p) (rs[p]-ls[p]+1)
int cover[N<<2|1],sum[N<<2|1],ls[N<<2|1],rs[N<<2|1];
inline void pushup(int p){sum[p]=sum[lc]+sum[rc];}
inline void pushnow(int p,const int &x){cover[p]=x;sum[p]=x*len(p);}
inline void pushdown(int p){if(cover[p]!=-1)pushnow(lc,cover[p]),pushnow(rc,cover[p]),cover[p]=-1;}
void build(int p,int l,int r){
cover[p]=-1;ls[p]=l,rs[p]=r;int mid=l+r>>1;
if(l==r)return (void)(sum[p]=val[l]);
build(lc,l,mid);build(rc,mid+1,r);pushup(p);
}
void change(int p,const int &ql,const int &qr,const int &x){
if(ql<=ls[p]&&qr>=rs[p])return pushnow(p,x);
pushdown(p);
int mid=ls[p]+rs[p]>>1;
if(ql<=mid)change(lc,ql,qr,x);
if(qr>mid)change(rc,ql,qr,x);
pushup(p);
}
int qsum(int p,const int &ql,const int & qr){
if(ql<=ls[p]&&qr>=rs[p])return sum[p];
int mid=ls[p]+rs[p]>>1;
pushdown(p);int ans=0;
if(ql<=mid)ans+=qsum(lc,ql,qr);
if(qr>mid)ans+=qsum(rc,ql,qr);
return ans;
}
}
using namespace sgt;
inline bool check(int mid){
for(int re i=1;i<=n;i++)val[i]=(a[i]>=mid);
build(1,1,n);
for(int re i=1;i<=m;i++){
int all=qsum(1,q[i].l,q[i].r);
if(q[i].op==1)change(1,q[i].l,q[i].l+all-1,1),change(1,q[i].l+all,q[i].r,0);
else change(1,q[i].l,q[i].r-all,0),change(1,q[i].r-all+1,q[i].r,1);
}
return qsum(1,k,k);
}
int main(){
n=red();m=red();
for(int re i=1;i<=n;i++)a[i]=red();
for(int re i=1;i<=m;i++)q[i].op=red(),q[i].l=red(),q[i].r=red();
int l=1,r=n,mid,ans;k=red();
while(l<=r){
mid=(l+r)>>1;
if(check(mid))l=mid+1,ans=mid;
else r=mid-1;
}
printf("%d",ans);
}
3.序列动态dp
一般只需要考虑合并dp时需要什么值,就维护什么值。
查询和修改的本质都是区间合并,所以能支持区间合并就可以上线段树。
例题:
WOJ2126 哨戒炮
WOJ2088 程序设计竞赛
均摊分析:
1.区间取模/区间求和
对于任意数x,当它比模数大时,它至少会变成原来的数的一半。所以对于每一个点,我们最多修改
O
(
l
o
g
)
O(log)
O(log)次。维护区间最大值,如果最大值大于模数,则暴力递归到叶子进行修改。每个点最多改log次,每次修改时
O
(
l
o
g
)
O(log)
O(log)的,所以时间复杂度
O
(
n
log
2
n
)
O(n\log ^2n)
O(nlog2n)。
2.区间除法/区间加
区间除法显然最多进行
O
(
l
o
g
)
O(log)
O(log)次。但是存在区间加法,除法次数就可能变大。对于线段树上一个节点,设其管辖区间内最大值为a,最小值为b,那么一次除法可以使其变为a/d和b/d,当这两者相同时,我们显然可以把区间除法变为区间减法。考虑着两者什么时候变相同:一次除法可以使最大值和最小值的差变为
a
/
d
−
b
/
d
=
(
a
−
b
)
/
d
a/d-b/d=(a-b)/d
a/d−b/d=(a−b)/d。所以除法最多log次,当前点的最大值和最小值之差就会减为0,由于我们分析的是最值之差,显然,对于要区间加法的节点,并不会影响其最大值和最小值的差,所以时间复杂度
O
(
n
log
2
n
)
O(n\log ^2n)
O(nlog2n)。
例题:WOJ2790 雅礼集训 市场
#include<bits/stdc++.h>
#define re register
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
const int N=1e5+5;
inline int red(){
int data=0;bool w=0;char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
int n,m,b,c;
typedef long long ll;
ll mn[N<<2|1],mx[N<<2|1],laz[N<<2|1],sum[N<<2|1],a[N];
inline void pushnow(int p,const ll&v,const int&l,const int&r){
laz[p]+=v;mn[p]+=v;mx[p]+=v;sum[p]+=v*(r-l+1);
}
inline void pushdown(int p,const int&l,const int&r){
if(laz[p]){
int mid=(l+r)>>1;
pushnow(lc,laz[p],l,mid);
pushnow(rc,laz[p],mid+1,r);
laz[p]=0;
}
}
inline void pushup(int p){
mn[p]=min(mn[lc],mn[rc]);
mx[p]=max(mx[lc],mx[rc]);
sum[p]=sum[lc]+sum[rc];
}
void build(int p,int l,int r){
if(l==r)return mn[p]=mx[p]=sum[p]=a[l],void();
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(p);
}
void change(int p,int l,int r,int ql,int qr,ll v){
if(ql<=l&&qr>=r)return pushnow(p,v,l,r);
int mid=(l+r)>>1;pushdown(p,l,r);
if(ql<=mid)change(lc,l,mid,ql,qr,v);
if(qr>mid)change(rc,mid+1,r,ql,qr,v);
pushup(p);
}
inline ll get(const ll&x,const ll&y){return ((ll)floor((double)x/y));}
void modify(int p,int l,int r,int ql,int qr,ll v){
if(ql<=l&&qr>=r&&get(mn[p],v)-mn[p]==get(mx[p],v)-mx[p])
return pushnow(p,get(mn[p],v)-mn[p],l,r);
pushdown(p,l,r);int mid=(l+r)>>1;
if(ql<=mid)modify(lc,l,mid,ql,qr,v);
if(qr>mid)modify(rc,mid+1,r,ql,qr,v);
pushup(p);
}
ll ask1(int p,int l,int r,int ql,int qr){
if(ql<=l&&qr>=r)return sum[p];
pushdown(p,l,r);
int mid=(l+r)>>1;ll ans=0;
if(ql<=mid)ans+=ask1(lc,l,mid,ql,qr);
if(qr>mid)ans+=ask1(rc,mid+1,r,ql,qr);
return ans;
}
inline void cmin(ll&x,const ll&y){(x>y)&&(x=y);}
ll ask2(int p,int l,int r,int ql,int qr){
if(ql<=l&&qr>=r)return mn[p];
pushdown(p,l,r);
int mid=(l+r)>>1;ll ans=1e15;
if(ql<=mid)cmin(ans,ask2(lc,l,mid,ql,qr));
if(qr>mid)cmin(ans,ask2(rc,mid+1,r,ql,qr));
return ans;
}
int main(){
n=red();m=red();
for(int re i=1;i<=n;i++)a[i]=(ll)red();
build(1,1,n);
while(m--)
switch(red()){
case 1:{
int l=red()+1,r=red()+1;
change(1,1,n,l,r,(ll)red());
break;
}
case 2:{
int l=red()+1,r=red()+1;
modify(1,1,n,l,r,(ll)red());
break;
}
case 3:{
int l=red()+1,r=red()+1;
cout<<ask2(1,1,n,l,r)<<"\n";
break;
}
case 4:{
int l=red()+1,r=red()+1;
cout<<ask1(1,1,n,l,r)<<"\n";
break;
}
}
}
3.区间开根
对于一个大小为1e9的数,开根5次就会变成1。当一个数是1或0时,开根无效。所以我们需要开根时可以暴力递归,维护区间最大值。如果最大值为1或0就不再递归。对于每个叶节点,最多被暴力递归5次,所以时间复杂度正确。
例题:WOJ3864 花神游历各国
4.模意义下区间变定常数指数
区间修改:对于每一个数
a
i
a_i
ai,变为
c
a
i
c^{a_i}
cai,c为一个全局的定值。
我们发现,若干次修改以后,
a
i
a_i
ai会变成
c
c
c
c
.
.
.
c
a
i
c^{c^{c^{c^{...^{^{c^{a_i}}}}}}}
cccc...cai。根据广义欧拉定理,这样的操作相当于对模数不停地取
ϕ
\phi
ϕ。当模数变为1时,区间修改就没有意义了。所以线段树每个叶节点被修改的次数就是模数能够取
ϕ
\phi
ϕ的次数。考虑一个数能够取
ϕ
\phi
ϕ的次数:当一个数是奇数时,取
ϕ
\phi
ϕ后会变成偶数;当一个数是偶数时,取
ϕ
\phi
ϕ后最大变为原来的一半(除去所有偶数)。所以一个数取
ϕ
\phi
ϕ最多能取
O
(
l
o
g
n
)
O(logn)
O(logn)次。所以对于一个数,只有log次修改时有效的。每次修改需要利用广义欧拉定理重新计算
c
c
c
c
.
.
.
c
a
i
c^{c^{c^{c^{...^{^{c^{a_i}}}}}}}
cccc...cai,至多有log层,每一层需要一个快速幂
O
(
l
o
g
n
)
O(logn)
O(logn),故叶子的一次修改是
O
(
log
2
n
)
O(\log^2n)
O(log2n)。每个叶子最多log次修改,时间复杂度
O
(
n
l
o
g
3
n
)
O(nlog^3n)
O(nlog3n)。注意到修改时的快速幂的底数只有log个,可以暴力预处理实现10000进制下O(1)快速幂。时间复杂度
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)。注意广义欧拉定理有两条,注意第二条的使用范围。
例题:【SHOI2017】相逢是问候
代码:
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=7e4+5;
int n,m,a[N],b,c;
typedef long long ll;
inline int red(){
int data=0;int w=1; char ch=0;
ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}bool vis[N],flag=0,f1[55][N>>2],f2[55][N>>2];
int p[N],pcnt=0,cnt=0,mod,pmod[N];
long long c1[55][N>>2],c2[55][N>>2];
inline int ksm(int b,const int&id){
flag=f1[id][b%10000]|f2[id][b/10000]|(c1[id][b%10000]*c2[id][b/10000]>=pmod[id]);
return c1[id][b%10000]*c2[id][b/10000]%pmod[id];
}
inline int phi(int mod){
int ans=mod;
for(int re i=1;p[i]*p[i]<=mod;i++){
if(mod%p[i]==0){
ans/=p[i];
ans*=(p[i]-1);
while(mod%p[i]==0)mod/=p[i];
}
}if(mod>1)ans/=mod,ans*=(mod-1);
return ans;
}
void pre(){
for(int re i=2;i^11000;i++){
if(!vis[i])p[++pcnt]=i;
for(int re j=1,x=p[j]*i;j<=pcnt&&x<11000;j++,x=p[j]*i){
vis[x]=1;
if(i%p[j]==0)break;
}
}
pmod[cnt=0]=mod;
while(pmod[cnt]!=1)++cnt,pmod[cnt]=phi(pmod[cnt-1]);
pmod[++cnt]=1;
for(int re i=0;i<=cnt;i++){
c1[i][0]=1;c2[i][0]=1;
for(int re j=1;j<=10000;j++){
c1[i][j]=c1[i][j-1]*c;
if(c1[i][j]>=pmod[i])f1[i][j]=1,c1[i][j]%=pmod[i];
f1[i][j]|=f1[i][j-1];
}
for(int re j=1;j<=10000;j++){
c2[i][j]=c2[i][j-1]*c1[i][10000];
if(c2[i][j]>=pmod[i])f2[i][j]=1,c2[i][j]%=pmod[i];
f2[i][j]|=f2[i][j-1];
}
}
}
inline int get(const int&a,const int&num){
int tmp=a;
if(tmp>pmod[num])tmp=tmp%pmod[num]+pmod[num];
for(int re i=num-1;~i;i--){
tmp=ksm(tmp,i);
if(flag)tmp+=pmod[i],flag=0;
}return tmp%mod;
}
namespace sgt{
#define lc (p<<1)
#define rc (p<<1|1)
int ls[N<<2|1],rs[N<<2|1],laz[N<<2|1],sum[N<<2|1];
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline void pushup(const int&p){
sum[p]=add(sum[lc],sum[rc]);
laz[p]=min(laz[lc],laz[rc]);
}
inline void build(int p,int l,int r){
ls[p]=l,rs[p]=r;
if(l==r)return(void)(sum[p]=a[l]);
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
inline void change(int p,const int&ql,const int&qr){
if(laz[p]>=cnt)return;
if(ls[p]==rs[p])return(void)(sum[p]=get(a[ls[p]],++laz[p]));
int mid=(ls[p]+rs[p])>>1;
if(ql<=mid)change(lc,ql,qr);
if(qr>mid)change(rc,ql,qr);
pushup(p);
}
inline int query(int p,const int&ql,const int&qr){
if(ql<=ls[p]&&qr>=rs[p])return sum[p];
int mid=(ls[p]+rs[p])>>1,ans=0;
if(ql<=mid)if((ans+=query(lc,ql,qr))>=mod)ans-=mod;
if(qr>mid)if((ans+=query(rc,ql,qr))>=mod)ans-=mod;
return ans;
}
}using namespace sgt;
int main()
{
n=red();m=red();mod=red();c=red();
for(int re i=1;i<=n;i++)a[i]=red();
build(1,1,n);pre();
while(m--){int l,r;
switch(red()){
case 0:l=red(),r=red(),change(1,l,r);break;
case 1:l=red(),r=red(),printf("%d\n",query(1,l,r));break;
}
}
}
其它操作
1.扫描线+线段树
求矩形面积并。
例题:WOJ1690
代码:
#include<bits/stdc++.h>
#define re register
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
const int N=1e3+5;
int n,m;
struct line{
double l,r;
double y;
int val;
friend inline bool operator <(const line &a,const line &b){return a.y<b.y;}
}l[N];
int cnt=0;
inline int red()
{
int data=0;int w=1; char ch=0;
ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
struct tree{int l,r,cover;double sum;}t[N];
double h[N];
int tot=0;
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r;
t[p].sum=t[p].cover=0;
if(l==r)return;
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
void pushup(int p)
{
if(t[p].cover) t[p].sum=h[t[p].r+1]-h[t[p].l];
else if(t[p].l==t[p].r)t[p].sum=0;
else t[p].sum=t[lc].sum+t[rc].sum;
}
void insert(int p,int l,int r,int val)
{
if(t[p].l>=l&&t[p].r<=r)
{
t[p].cover+=val;
pushup(p);
return;
}
int mid=(t[p].l+t[p].r)>>1;
if(l<=mid)insert(lc,l,r,val);
if(r>mid)insert(rc,l,r,val);
pushup(p);
}
double x_1,x_2,y_1,y_2;
double ans=0;
int main()
{
while((n=red())&&n)
{
ans=0;
cnt=0;tot=0;
for(int re i=1;i<=n;i++)
{
scanf("%lf%lf%lf%lf",&x_1,&y_1,&x_2,&y_2);
l[++cnt]=(line){x_1,x_2,y_1,1};
l[++cnt]=(line){x_1,x_2,y_2,-1};
h[++tot]=x_2;
h[++tot]=x_1;
}
sort(l+1,l+cnt+1);
sort(h+1,h+tot+1);
tot=unique(h+1,h+tot+1)-h-1;
build(1,1,tot);
for(int re i=1;i<=cnt;i++)
{
double a=l[i].l,b=l[i].r;
int ll=lower_bound(h+1,h+tot+1,a)-h,rr=lower_bound(h+1,h+tot+1,b)-h-1;
insert(1,ll,rr,l[i].val);
ans+=t[1].sum*(l[i+1].y-l[i].y);
}
printf("%.2lf\n",ans);
}
}
2.李超线段树
平面内有一些线段。支持:
1.加入一根线段
2.询问线段上坐标x的最高的y的值
本质就是一个标记永久化的线段树。对于一个节点维护区间[l,r],我们在这个节点维护x=mid处y最大的一个线段。对于一个完全覆盖了当前节点的修改:如果当前线段把该节点维护的线段踩爆了,就直接覆盖,不再递归。否则比较两个线段在mid处的值,在当前节点留下mid处最大的线段。对于另一个线段,它只可能对左儿子或右儿子有贡献(可以画图理解),递归修改即可。对于一次修改,会覆盖log个区间,每个区间递归修改的时间为log,所以时间复杂度
O
(
n
log
2
n
)
O(n\log^2n)
O(nlog2n)。单点询问就是到叶节点路径上所有点维护的线段在x处的取值取max。
例题:
WOJ2795 雅礼集训 线段游戏
WOJ2561 SDOI2016游戏
代码:(线段游戏)
#include<bits/stdc++.h>
#define re register
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
const int N=2e5+5;
const double inf=1e10;
typedef long long ll;
inline int red(){
int re data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
int n,m,a,b,c;
inline void cmax(double&x,const double&y){(x<y)&&(x=y);}
struct line{
int x1;double y1;
int x2;double y2;
inline double get(int x){
if(x<x1||x>x2)return -inf;
if(x1==x2)return max(y1,y2);
return 1.0*(x-x1)/(x2-x1)*y2+1.0*(x2-x)/(x2-x1)*y1;
}inline double k(){return (y2-y1)/(x2-x1);}
}laz[N<<2|1];
void build(int p,int l,int r){
laz[p]=(line){-10000000,-1e10,100000000,-1e10};
if(l==r)return;int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
}
void change(int p,int l,int r,line nw){
int ql=nw.x1,qr=nw.x2;if(qr<l||ql>r)return;
if(ql<=l&&qr>=r){
if(nw.get(l)>laz[p].get(l)&&nw.get(r)>laz[p].get(r))laz[p]=nw;
else if(l<r){
int mid=(l+r)>>1;
if(nw.get(mid)>laz[p].get(mid))swap(nw,laz[p]);
if(nw.k()>laz[p].k())change(rc,mid+1,r,nw);
else change(lc,l,mid,nw);
}return;
}int mid=(l+r)>>1;
if(ql<=mid)change(lc,l,mid,nw);
if(qr>mid)change(rc,mid+1,r,nw);
}
double query(int p,int l,int r,int pos){
double ans=laz[p].get(pos);
if(l==r)return ans;int mid=(l+r)>>1;
if(pos<=mid)cmax(ans,query(lc,l,mid,pos));
else cmax(ans,query(rc,mid+1,r,pos));
return ans;
}
int main(){
n=red();m=red();build(1,0,100000);
for(int re i=1;i<=n;i++){
line l=(line){red(),1.0*red(),red(),1.0*red()};
if(l.x1>l.x2)swap(l.x1,l.x2),swap(l.y1,l.y2);
change(1,0,100000,l);
}double ans;
for(int re i=1;i<=m;i++){
if(red())printf("%.6f\n",(ans=query(1,0,100000,red()))>-1e9?ans:0.0);
else{
line l=(line){red(),1.0*red(),red(),1.0*red()};
if(l.x1>l.x2)swap(l.x1,l.x2),swap(l.y1,l.y2);
change(1,0,100000,l);
}
}
}
3.线段树合并
如果任意时刻合并的线段树无公共叶节点,那么总时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。相当于把根节点到每个叶节点的路径的长度加起来就是总合并时间复杂度。线段树合并也可以用于优化dp。
例题:
WOJ2036 【HNOI2012】永无乡
WOJ3480「PKUWC 2018」Minimax
代码:(永无乡)
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+5;
int n,m,a,b;char c;
inline int red()
{
int data=0;int w=1; char ch=0;
ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
int fa[N],siz[N*20+1],lc[N*20+1],rc[N*20+1],cnt=0,idx[N],rank[N],rt[N];
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void build(int &k,int l,int r,int rk)
{
k=++cnt;
if(l==r)return (void)(++siz[k]);
int mid=(l+r)>>1;
if(rk<=mid)build(lc[k],l,mid,rk);
else build(rc[k],mid+1,r,rk);
siz[k]=siz[lc[k]]+siz[rc[k]];
}
inline int merge(int x,int y)
{
if(!x||!y)return x+y;siz[x]+=siz[y];
lc[x]=merge(lc[x],lc[y]);
rc[x]=merge(rc[x],rc[y]);
return x;
}
inline int query(int k,int l,int r,int rk)
{
if(l==r)return l;int mid=(l+r)>>1;
if(rk>siz[lc[k]])return query(rc[k],mid+1,r,rk-siz[lc[k]]);
else return query(lc[k],l,mid,rk);
}
inline void link(int a,int b)
{
int f=find(a),fb=find(b);if(f==fb)return;
rt[f]=merge(rt[f],rt[fb]);fa[fb]=f;
}
int main()
{
n=red();m=red();
for(int re i=1;i<=n;i++)rank[i]=red(),idx[rank[i]]=i,fa[i]=i,build(rt[i],1,n,rank[i]);
for(int re i=1;i<=m;i++)link(red(),red());
m=red();
for(int re i=1;i<=m;i++)
{
scanf("\n%c",&c);
a=red();b=red();
if(c=='B')link(a,b);
else printf("%d\n",siz[rt[find(a)]]>=b?idx[query(rt[find(a)],1,n,b)]:-1);
}
}
4.线段树分治
删除操作->加入操作
加入删除操作->加入撤销操作
例题:
WOJ4492 二分图
WOJ4709 迷路
5.线段树(主席树)优化建图
点->区间连边
区间->点连边
区间->区间连边
例题:
WOJ4751 上网
WOJ4737 Oleg and chess
6.线段树维护具有单调性的集合
这种题目并没有其他类型的题目那样看起来很"线段树"。只是分析题目或者把维护的集合里的元素进行排序发现可以把题目里的操作转化成线段树的操作。
例题:
WOJ2977 「AHOI2014」 奇怪的计算器
代码:
#include<bits/stdc++.h>
#define re register
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
const int N=1e5+5;
typedef long long ll;
inline int red(){
int data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}int n,m,b,c,id[N];
ll mn[N<<2|1],mx[N<<2|1],laz1[N<<2|1],laz2[N<<2|1],laz3[N<<2|1],num[N],L,R,a[N];
inline bool cmp(const int&x,const int&y){return a[x]<a[y];}
void build(int p,int l,int r){
mn[p]=a[id[l]];mx[p]=a[id[r]];laz1[p]=1;
if(l==r)return;int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
}
inline void pushnow(const int&p,const int&l,const int&r,const ll&a1,const ll&a2,const ll&a3){
laz1[p]*=a1;(laz2[p]*=a1)+=a2;(laz3[p]*=a1)+=a3;
(mn[p]*=a1)+=a2*a[id[l]]+a3;(mx[p]*=a1)+=a2*a[id[r]]+a3;
}
inline void pushdown(int p,const int&l,const int&r){
if(laz1[p]!=1||laz2[p]||laz3[p]){
int mid=(l+r)>>1;
pushnow(lc,l,mid,laz1[p],laz2[p],laz3[p]);
pushnow(rc,mid+1,r,laz1[p],laz2[p],laz3[p]);
laz1[p]=1;laz2[p]=0;laz3[p]=0;
}
}
inline void cov1(int p,int l,int r){
if(l==r)return pushnow(p,l,r,0,0,L);
pushdown(p,l,r);int mid=(l+r)>>1;
if(mn[rc]<L)pushnow(lc,l,mid,0,0,L),cov1(rc,mid+1,r);
else cov1(lc,l,mid);
mn[p]=mn[lc];mx[p]=mx[rc];
}
inline void cov2(int p,int l,int r){
if(l==r)return pushnow(p,l,r,0,0,R);
pushdown(p,l,r);int mid=(l+r)>>1;
if(mx[lc]>R)pushnow(rc,mid+1,r,0,0,R),cov2(lc,l,mid);
else cov2(rc,mid+1,r);
mn[p]=mn[lc];mx[p]=mx[rc];
}ll ans[N];
void dfs(int p,int l,int r){
if(l==r)return void(ans[id[l]]=mx[p]);
pushdown(p,l,r);int mid=(l+r)>>1;
dfs(lc,l,mid);dfs(rc,mid+1,r);
}
char s[3],op[N];
int main(){
n=red();L=red();R=red();
for(int re i=1;i<=n;i++)scanf("%s%lld",s,&num[i]),op[i]=s[0];
m=red();for(int re i=1;i<=m;i++)a[i]=red(),id[i]=i;
sort(id+1,id+m+1,cmp);build(1,1,m);
for(int re i=1;i<=n;i++){
switch(op[i]){
case'+':{pushnow(1,1,m,1,0,num[i]);break;}
case'-':{pushnow(1,1,m,1,0,-num[i]);break;}
case'*':{pushnow(1,1,m,num[i],0,0);break;}
case'@':{pushnow(1,1,m,1,num[i],0);break;}
}if(mn[1]<L)cov1(1,1,m);if(mx[1]>R)cov2(1,1,m);
}dfs(1,1,m);
for(int re i=1;i<=m;i++)
cout<<ans[i]<<"\n";
}
WOJ3609 [PA2015]Siano
代码:
#include<bits/stdc++.h>
#define re register
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
const int N=5e5+5;
typedef long long ll;
inline ll red(){
ll data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}int n,m,b,c,id[N];
ll a[N],mn[N<<2|1],mx[N<<2|1],sum[N<<2|1],laz1[N<<2|1],laz2[N<<2|1],laz3[N<<2|1],suma[N<<2|1];
inline void pushup(int p){
mn[p]=mn[lc];mx[p]=mx[rc];
sum[p]=sum[lc]+sum[rc];
suma[p]=suma[lc]+suma[rc];
}
void build(int p,int l,int r){
laz1[p]=1;
if(l==r)return void(suma[p]=a[id[l]]);
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(p);
}
inline void pushnow(int p,const int&l,const int&r,const ll&k1,const ll&k2,const ll&k3){
laz1[p]*=k1;(laz2[p]*=k1)+=k2;(laz3[p]*=k1)+=k3;
(mn[p]*=k1)+=k2+k3*a[id[l]];(mx[p]*=k1)+=k2+k3*a[id[r]];
(sum[p]*=k1)+=k2*(r-l+1)+k3*suma[p];
}
ll ans=0,d[N];
inline void pushdown(int p,const int&l,const int&r){
if(laz1[p]!=1||laz2[p]||laz3[p]){
int mid=(l+r)>>1;
pushnow(lc,l,mid,laz1[p],laz2[p],laz3[p]);
pushnow(rc,mid+1,r,laz1[p],laz2[p],laz3[p]);
laz1[p]=1;laz2[p]=0;laz3[p]=0;
}
}
void cov(int p,int l,int r,const ll&lim){
if(l==r){
ans+=sum[p]-lim;
pushnow(p,l,r,0,lim,0);
return;
}int mid=(l+r)>>1;pushdown(p,l,r);
if(mx[lc]>lim)ans+=sum[rc]-lim*(r-mid),pushnow(rc,mid+1,r,0,lim,0),cov(lc,l,mid,lim);
else cov(rc,mid+1,r,lim);
pushup(p);
}
inline bool cmp(const int&x,const int&y){return a[x]<a[y];}
int main(){n=red();m=red();
for(int re i=1;i<=n;i++)a[i]=red(),id[i]=i;
sort(id+1,id+n+1,cmp);build(1,1,n);
for(int re i=1;i<=m;i++){
d[i]=red();ll lim=red();
ans=0;pushnow(1,1,n,1,0,d[i]-d[i-1]);
if(mx[1]>lim)cov(1,1,n,lim);
cout<<ans<<"\n";
}
}
二.树状数组
1.单点修改+区间求和
2.区间修改+区间求和
3.二维区间修改+二维区间求和
4.树上:单点修改+链查询
5.树上:子树修改+单点查询
6.树上:链修改+子树求和
例题:1935 魔法树
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#define re register
using namespace std;
long long n,m,a,b,s,t;
const long long N=2e5+1;
long long f[N];
long long g[N];
long long nxp[N<<2|1];
long long cnt=0,tot=0;
struct ndeo{
long long u,v;
}e[N];
long long idx=0;
inline long long low(long long x){return x&(-x);}
struct tree{
long long c[N];
inline void change(long long k,long long v)
{
while(k<=idx)
{
c[k]+=v;
k+=low(k);
}
}
inline long long ask(long long k)
{
long long ret=0;
while(k>0)
{
ret+=c[k];
k-=low(k);
}
return ret;
}
}t1,t2;//t1:w,t2:(dep(x)+1)*w
inline void add(long long u,long long v)
{
e[++cnt].u=u;
e[cnt].v=v;
nxp[cnt]=f[u];
f[u]=cnt;
}
long long fa[N][20];
long long st[N];
long long ed[N];
long long dep[N];
void dfs(long long u)
{
st[u]=++idx;
for(long long re i=1;(1<<i)<=dep[u];i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(long long re i=f[u];i;i=nxp[i])
{
long long v=e[i].v;
if(!dep[v])
{
dep[v]=dep[u]+1;
fa[v][0]=u;
dfs(v);
}
}
ed[u]=idx;
}
long long lca(long long a,long long b)
{
if(dep[a]<dep[b])swap(a,b);
long long t=dep[a]-dep[b];
for(long long re i=0;(1<<i)<=t;i++)
if(t&(1<<i))a=fa[a][i];
if(a==b)return a;
for(long long re i=18;i>=0;i--)
{
if(fa[a][i]!=fa[b][i])
{
a=fa[a][i];
b=fa[b][i];
}
}
return fa[a][0];
}
char p;
int main()
{
scanf("%lld",&n);
for(long long re i=1;i<n;i++)
{
scanf("%lld%lld",&a,&b);a++;b++;
add(a,b);add(b,a);
}
scanf("%lld",&m);
dep[1]=1;
dfs(1);
for(long long re i=1;i<=m;i++)
{
long long z;
scanf("\n%c",&p);
if(p=='A')
{
scanf("%lld%lld%lld",&a,&b,&z);a++,b++;
long long lc=lca(a,b);
t1.change(st[a],z);
t2.change(st[a],z*(dep[a]+1));
t1.change(st[b],z);
t2.change(st[b],z*(dep[b]+1));
t1.change(st[lc],-z);
t2.change(st[lc],-z*(dep[lc]+1));
if(lc!=1)
t1.change(st[fa[lc][0]],-z),
t2.change(st[fa[lc][0]],-z*(dep[fa[lc][0]]+1));
}
else
{
scanf("%lld",&a);a++;
printf("%lld\n",t2.ask(ed[a])-t2.ask(st[a]-1)-dep[a]*(t1.ask(ed[a])-t1.ask(st[a]-1)));
}
}
}
7.树上:链修改+单点查询
8.树上:子树修改+链查询
例题:WOJ4695 fibonacci
代码:
#include<bits/stdc++.h>
#define re register
#define add(a,b) ((a+b)%mod)
#define dec(a,b) ((a-b)%mod+mod)%mod
using namespace std;
const int N=1e5+5;
const int mod=1e9+7;
int n,m;
inline int red(){
int data=0;int w=1;char ch=0;
ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9')data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
inline int ksm(long long a,int b){
int ret=1;
while(b){
if(b&1)ret=1ll*ret*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ret;
}
inline int inv(long long a){return ksm(a,mod-2);}
struct num{
int a,b;
friend inline num operator+(const num&a,const num&b){return (num){add(a.a,b.a),add(a.b,b.b)};}
friend inline num operator-(const num&a,const num&b){return (num){dec(a.a,b.a),dec(a.b,b.b)};}
friend inline num operator+(const num&a,const int&b){return (num){add(a.a,b),a.b};}
friend inline num operator-(const num&a,const int&b){return (num){dec(a.a,b),a.b};}
friend inline num operator*(const num&a,const num&b){return (num){(1ll*a.a*b.a+5ll*a.b*b.b)%mod,(1ll*a.b*b.a+1ll*a.a*b.b)%mod};}
friend inline num operator*(const num&a,const int&b){return (num){1ll*a.a*b%mod,1ll*a.b*b%mod};}
friend inline num operator/(const num&a,const num&b){return (num){dec(1ll*a.a*b.a%mod,5ll*a.b*b.b%mod),dec(1ll*a.b*b.a%mod,1ll*a.a*b.b%mod)}*inv(dec(1ll*b.a*b.a,5ll*b.b*b.b));}
friend inline num operator^(const num&a,const long long&b){
long long t=b;num ret=(num){1,0},g=a;
if(t<0)g=(num){1,0}/g,t=-t;
while(t){if(t&1)ret=ret*g;g=g*g;t>>=1;}
return ret;
}
};
const num x=(num){inv(2),inv(2)};
const num y=(num){inv(2),-inv(2)};
struct bit{
num c[N];
inline void adde(int pos,num b){
while(pos<=n)c[pos]=c[pos]+b,pos+=(pos&(-pos));
}
inline num query(int pos){
num x=(num){0,0};
while(pos)x=x+c[pos],pos-=(pos&(-pos));
return x;
}
}A,B,C,D;
vector<int>g[N];
int fa[N][20],st[N],ed[N],rev[N],tot=0,dep[N],a;
long long b;
inline num detx1(long long k,int deep){return x^(k-deep+1);}
inline num dety1(long long k,int deep){return y^(k-deep+1);}
inline num detx2(long long k){return x^k;}
inline num dety2(long long k){return y^k;}
void dfs(int u){
for(int re i=1;(1ll<<i)<=dep[u];i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
st[u]=++tot;rev[tot]=u;
for(int re i=g[u].size()-1;~i;--i)
dep[g[u][i]]=dep[u]+1,dfs(g[u][i]);
ed[u]=tot;
}
inline int lca(int a,int b){
if(dep[a]<dep[b])swap(a,b);
int t=dep[a]-dep[b];
for(int re i=0;(1<<i)<=t;i++)
if(t&(1<<i))a=fa[a][i];
if(a==b)return a;
for(int re i=18;i>=0;--i)
if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i];
return fa[a][0];
}
char s[5];
inline num get(int a,int b,int lc){
num anx=A.query(st[a])*(x^(dep[a]))+A.query(st[b])*(x^dep[b])-A.query(st[lc])*(x^dep[lc])-A.query(st[fa[lc][0]])*(x^dep[fa[lc][0]]);
num any=B.query(st[a])*(y^(dep[a]))+B.query(st[b])*(y^dep[b])-B.query(st[lc])*(y^dep[lc])-B.query(st[fa[lc][0]])*(y^dep[fa[lc][0]]);
anx=anx-(C.query(st[a])+C.query(st[b])-C.query(st[lc])-C.query(st[fa[lc][0]]));anx=anx/(x-1);
any=any-(D.query(st[a])+D.query(st[b])-D.query(st[lc])-D.query(st[fa[lc][0]]));any=any/(y-1);
return anx-any;
}
signed main()
{
n=red();m=red();
for(int re i=2;i<=n;i++)fa[i][0]=red(),g[fa[i][0]].push_back(i);
dep[1]=1;dfs(1);
while(m--){
scanf("%s%d%lld",s,&a,&b);
if(s[0]=='Q')cout<<get(a,b,lca(a,b)).b<<"\n";
else{
A.adde(st[a],detx1(b,dep[a]));
B.adde(st[a],dety1(b,dep[a]));
A.adde(ed[a]+1,(num){0,0}-detx1(b,dep[a]));
B.adde(ed[a]+1,(num){0,0}-dety1(b,dep[a]));
C.adde(st[a],detx2(b));
D.adde(st[a],dety2(b));
C.adde(ed[a]+1,(num){0,0}-detx2(b));
D.adde(ed[a]+1,(num){0,0}-dety2(b));
}
}
}
9.优化dp
例题:
WOJ3996 折线统计
代码:
#include<bits/stdc++.h>
#define re register
#define mp make_pair
using namespace std;
const int N=1e5+5,mod=1e5+7;
inline int red(){
int data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}int n,m,mx;
struct point{
int x,y;
friend inline bool operator<(const point&a,const point&b){return a.x<b.x;}
}p[N];
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a<b?a-b+mod:a-b;}
inline void Add(int&a,const int&b){((a+=b)>=mod)&&(a-=mod);}
struct tree{
int c[N];
inline void add(int x,const int&v){while(x<=mx)Add(c[x],v),x+=x&(-x);}
inline int query(int x){int ret=0;
while(x)Add(ret,c[x]),x-=x&(-x);return ret;
}
}bit[11][2];
int main(){n=red();m=red();
for(int re i=1;i<=n;i++)p[i]=(point){red(),red()},mx=max(mx,p[i].y);
sort(p+1,p+n+1);
for(int re i=1;i<=n;i++){
bit[0][0].add(p[i].y,1);
bit[0][1].add(p[i].y,1);
for(int re k=1;k<=m;k++){
bit[k][0].add(p[i].y,add(bit[k][0].query(p[i].y-1),bit[k-1][1].query(p[i].y-1)));
bit[k][1].add(p[i].y,add(dec(bit[k][1].query(mx),bit[k][1].query(p[i].y)),dec(bit[k-1][0].query(mx),bit[k-1][0].query(p[i].y))));
}
}cout<<add(bit[m][0].query(mx),bit[m][1].query(mx))<<"\n";
}
WOJ4757 删数字
代码:
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+5;
int n,m,c[N];
inline int red(){
int re data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
inline void cmax(int&x,const int&y){(x<y)&&(x=y);}
inline void add(int x,int v){
while(x<=n)cmax(c[x],v),x+=x&-x;
}
inline int query(int x){
int ret=0;
while(x)cmax(ret,c[x]),x-=x&-x;
return ret;
}
int f[N],a[N],x,b[N];
vector<int>g[N];
int main(){
n=red();
for(int re i=1;i<=n;i++)a[i]=red(),g[a[i]].push_back(i);
for(int re i=1;i<=n;i++){
for(int re j=g[i].size()-1;~j;--j){
int id=g[i][j];
if(id>=a[id])f[id]=query(id-a[id]+1)+1;
}
for(int re j=g[i].size()-1;~j;--j){
int id=g[i][j];
if(id>=a[id])add(id-a[id]+1,f[id]);
}
}
cout<<query(n)<<"\n";
}
WOJ2439 最长递增子序列的数量
代码:
#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define re register
using namespace std;
const int N=1e5+5,mod=1e9+7;
int n,m,a[N],b[N];
tr1::unordered_map<int,int>f;
inline int red(){
int re data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
inline int add(const int&a,const int&b){
return a+b>=mod?a+b-mod:a+b;
}
struct node{
int len,cnt;
node(int a=0,int b=0){len=a,cnt=b;}
friend inline node operator+(node a,node b){
if(a.len==b.len)return node(a.len,add(a.cnt,b.cnt));
if(a.len<b.len)swap(a,b);
return node(a.len,a.cnt);
}
}c[N];
inline void add(int x,node v){
while(x<=m)c[x]=c[x]+v,x+=x&-x;
}
inline node query(int x){
node ret(0,1);
while(x){
ret=ret+c[x];
x-=x&-x;
}return ret;
}
int main(){
n=red();
for(int re i=1;i<=n;i++)b[i]=a[i]=red();
sort(b+1,b+n+1);m=unique(b+1,b+n+1)-b-1;
for(int re i=1;i<=m;i++)f[b[i]]=i;
for(int re i=1;i<=n;i++)a[i]=f[a[i]];
for(int re i=1;i<=n;i++){
node x=query(a[i]-1);++x.len;
add(a[i],x);
}cout<<query(m).cnt<<"\n";
}
WOJ3242 雅礼集训 数列
代码:
#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define re register
using namespace std;
const int N=4e5+5,mod=1e9+7;
int n,m,a[N],b[N];
tr1::unordered_map<int,int>f;
inline int red(){
int re data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
inline int add(const int&a,const int&b){
return a+b>=mod?a+b-mod:a+b;
}inline int dec(const int&a,const int&b){
return a<b?a-b+mod-1:a-b;
}
struct node{
int len,cnt;
node(int a=0,int b=0){len=a,cnt=b;}
friend inline node operator+(node a,node b){
if(a.len==b.len)return node(a.len,add(a.cnt,b.cnt));
if(a.len<b.len)swap(a,b);
return node(a.len,a.cnt);
}
}c[N];
inline void add(int x,node v){
while(x<=m)c[x]=c[x]+v,x+=x&-x;
}
inline node query(int x){
node ret(0,1);
while(x){
ret=ret+c[x];
x-=x&-x;
}return ret;
}
inline int mul(const int&a,const int&b){
return 1ll*a*b%mod;
}
inline int ksm(int a,int b){
int ret=1;
while(b){
if(b&1)ret=mul(ret,a);
a=mul(a,a);b>>=1;
}
return ret;
}
int main(){
n=red();
for(int re i=n+1;i<=(n<<1);i++)b[i-n]=a[i]=red();
for(int re i=n;i;i--)a[i]=a[(n<<1)-i+1];
sort(b+1,b+n+1);m=unique(b+1,b+n+1)-b-1;
for(int re i=1;i<=m;i++)f[b[i]]=i;
for(int re i=1;i<=(n<<1);i++)a[i]=f[a[i]];
for(int re i=1;i<=(n<<1);i++){
node x=query(a[i]-1);++x.len;
add(a[i],x);
}node ans=query(m);
cout<<ans.len<<" "<<mul(ans.cnt,ksm(2,dec(n,ans.len+1)))<<"\n";
}
三.树链剖分
1.重链剖分
重链剖分是dfs序的升级版。
例题:
WOJ3963 [LNOI2014]LCA
WOJ4491「GXOI / GZOI2019」旧词
WOJ2888 【HNOI2015】开店
WOJ3652 洪水(ddp)
WOJ4773 找宝藏
WOJ2984 「JLOI2014」松鼠的新家
WOJ3558 「TJOI2018」xor
2.长链剖分
一般用来优化转移复杂度与深度有关的dp。
O(1)求k级祖先: lxhgww的奇思妙想
优化dp:
WOJ4414 秘术(天文密葬法)
WOJ4702 看门人
WOJ4467【十二省联考2019】希望
WOJ3604 [POI2014]HOT_Hotels
WOJ4317 谈笑风生
WOJ2500 重建计划
四.主席树
例题:WOJ4499 Spoj 10628. Count on a tree
主席树查询路径第k大模板。
代码:
#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define re register
using namespace std;
const int N=1e5+5;
int n,m,a,b,k,c[N],val[N],tot,rt[N],las;
tr1::unordered_map<int,int>f;
inline int red()
{
int data=0;int w=1; char ch=0;
ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data*w;
}
namespace tree{
int ch[N*30|1][2],siz[N*30|1],cnt=0;
int change(int v,int l,int r,int pos)
{
int u=++cnt;siz[u]=siz[v]+1;
if(l==r)return cnt;
int mid=(l+r)>>1;
ch[u][0]=ch[v][0];ch[u][1]=ch[v][1];
if(pos<=mid)ch[u][0]=change(ch[v][0],l,mid,pos);
else ch[u][1]=change(ch[v][1],mid+1,r,pos);
return u;
}
int query(int a,int b,int c,int d,int l,int r,int k)
{
if(l==r)return l;
int ls=siz[ch[a][0]]+siz[ch[b][0]]-siz[ch[c][0]]-siz[ch[d][0]];
int mid=(l+r)>>1;
if(ls>=k)return query(ch[a][0],ch[b][0],ch[c][0],ch[d][0],l,mid,k);
else return query(ch[a][1],ch[b][1],ch[c][1],ch[d][1],mid+1,r,k-ls);
}
}
using tree::change;
using tree::query;
namespace LCA{
vector<int>g[N];
int dep[N]={0,1},fa[N][18];
void dfs(int u){
rt[u]=change(rt[fa[u][0]],1,tot,f[val[u]]);
for(int re i=1;(1<<i)<=(dep[u]);i++)fa[u][i]=fa[fa[u][i-1]][i-1];
for(int re i=g[u].size()-1;~i;--i){int v=g[u][i];if(!dep[v])dep[v]=dep[u]+1,fa[v][0]=u,dfs(v);}
}
inline int lca(int a,int b)
{
if(dep[a]<dep[b])swap(a,b);
int t=dep[a]-dep[b];
for(int re i=0;(1<<i)<=t;++i)
if(t&(1<<i))a=fa[a][i];
if(a==b)return a;
for(int re i=17;~i;--i)
if(fa[a][i]!=fa[b][i])a=fa[a][i],b=fa[b][i];
return fa[a][0];
}
}
using LCA::g;
using LCA::dfs;
using LCA::lca;
int main()
{
n=red();m=red();
for(int re i=1;i<=n;i++)c[i]=val[i]=red();
sort(c+1,c+(tot=n)+1);tot=unique(c+1,c+n+1)-c-1;
for(int re i=1;i<=tot;i++)f[c[i]]=i;
for(int re i=1;i^n;i++)a=red(),b=red(),g[a].push_back(b),g[b].push_back(a);dfs(1);
while(m--){
a=red()^las;b=red();k=red();int lc1=lca(a,b),lc2=LCA::fa[lc1][0];
printf("%d",las=c[query(rt[a],rt[b],rt[lc1],rt[lc2],1,tot,k)]);if(m)putchar('\n');
}
}
WOJ1458 [SCOI2016]美味
主席树+按位贪心
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=2e5+5;
int n,m,ch[N*20|1][2],tot=1,rt[N]={1},siz[N*20|1],a,b,x,l,r,ans;
inline int red(){
int data=0;int w=1; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w==1?data:-data;
}
inline int lc(const int&x){return ch[x][0];}
inline int rc(const int&x){return ch[x][1];}
int change(int v,int l,int r,const int &pos){
int u=++tot;siz[u]=siz[v]+1;if(l==r)return u;
ch[u][0]=lc(v),ch[u][1]=rc(v);int mid=(l+r)>>1;
if(pos<=mid)ch[u][0]=change(lc(v),l,mid,pos);
else ch[u][1]=change(rc(v),mid+1,r,pos);
return u;
}
bool query(int u,int v,int l,int r,const int&ql,const int &qr){
if(ql>r||qr<l)return 0;if(ql<=l&&qr>=r)return siz[u]-siz[v]>0;int mid=l+r>>1;
return query(lc(u),lc(v),l,mid,ql,qr)||query(rc(u),rc(v),mid+1,r,ql,qr);
}
int main(){
n=red();m=red();
for(int re i=1;i<=n;i++)rt[i]=change(rt[i-1],0,132000,red());
while(m--){
b=red();x=red();l=red();r=red();ans=0;
for(int re j=17;~j;--j)
if((b&(1<<j))&&!query(rt[r],rt[l-1],0,132000,max(0,ans-x),min(132000,ans+(1<<j)-1-x)))ans|=1<<j;
else if(!(b&(1<<j))&&query(rt[r],rt[l-1],0,132000,max(0,ans+(1<<j)-x),min(132000,ans+(1<<j+1)-1-x)))ans|=1<<j;
cout<<(ans^b)<<"\n";
}
}
WOJ3587 【SDOI2013】森林
主席树路径第k大+启发式合并。
#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define re register
using namespace std;
const int N=1e5+5;
tr1::unordered_map<int,int>lsr;
inline int red(){
int data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
int n,las=0,cnt=0,q,m,a,b[N],c,dep[N],fa[N][17],sz[N],col[N],siz[N*205|1],rt[N],to[N<<1|1],f[N],nxp[N<<1|1],tot=0,ch[N*205|1][2],val[N];
inline int copy(int v){int u=++tot;ch[u][0]=ch[v][0];ch[u][1]=ch[v][1];siz[u]=siz[v];return tot;}
inline void add(int&u,int v,int l,int r,int w){
u=copy(v);++siz[u];
if(l==r)return;int mid=(l+r)>>1;
if(w<=mid)add(ch[u][0],ch[v][0],l,mid,w);
else add(ch[u][1],ch[v][1],mid+1,r,w);
}
inline int query(int u,int v,int l1,int l2,int l,int r,int k){
if(l==r)return l;int mid=(l+r)>>1,size=siz[ch[u][0]]+siz[ch[v][0]]-siz[ch[l1][0]]-siz[ch[l2][0]];
if(size>=k)return query(ch[u][0],ch[v][0],ch[l1][0],ch[l2][0],l,mid,k);
else return query(ch[u][1],ch[v][1],ch[l1][1],ch[l2][1],mid+1,r,k-size);
}
void dfs(int u,int ff,const int&cl){
dep[u]=dep[ff]+1;fa[u][0]=ff;col[u]=cl;
add(rt[u],rt[ff],1,n,lsr[val[u]]);
for(int re i=1;i<17;++i)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int re i=f[u];i;i=nxp[i])
if(to[i]!=ff)dfs(to[i],u,cl);
}
inline void add(int u,int v){
to[++cnt]=v;nxp[cnt]=f[u];f[u]=cnt;
to[++cnt]=u;nxp[cnt]=f[v];f[v]=cnt;
if(sz[col[u]]<sz[col[v]])u^=v^=u^=v;
sz[col[u]]+=sz[col[v]];
dfs(v,u,col[u]);
}
inline int lca(int u,int v){
if(dep[u]<dep[v])u^=v^=u^=v;
int t=dep[u]-dep[v];
for(int re i=0;(1<<i)<=t;++i)
if(t&(1<<i))u=fa[u][i];
if(u==v)return u;
for(int re i=16;~i;--i)
if(fa[u][i]!=fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}char s[5];
int main(){red();
n=red();m=red();q=red();
for(int re i=1;i<=n;i++)b[i]=val[i]=red();
sort(b+1,b+n+1);int all=unique(b+1,b+n+1)-b-1;
for(int re i=1;i<=all;i++)lsr[b[i]]=i;
for(int re i=1;i<=n;i++)add(rt[i],0,1,n,lsr[val[i]]),dep[i]=1,col[i]=i,sz[i]=1;
for(int re i=1;i<=m;i++)add(red(),red());
while(q--){
scanf("%s",s);int u=red()^las,v=red()^las;
if(s[0]=='Q'){int lc=lca(u,v);
cout<<(las=b[query(rt[u],rt[v],rt[lc],rt[fa[lc][0]],1,n,red()^las)])<<"\n";
}else add(u,v);
}
}
WOJ4736 The Winds of Winter
主席树查询前驱后继。
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+5;
const int M=N<<6|1;
int n,m;
inline int red(){
int data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}int rt1[M],rt2[M],ch[M][2],tot=0,siz[M];
int add(int v,int l,int r,const int&pos){
int u=++tot;siz[u]=siz[v]+1;
ch[u][0]=ch[v][0];ch[u][1]=ch[v][1];
if(l==r)return u;int mid=(l+r)>>1;
if(pos<=mid)ch[u][0]=add(ch[v][0],l,mid,pos);
else ch[u][1]=add(ch[v][1],mid+1,r,pos);
return u;
}
int query1(int u,int v,int l,int r,const double&pos){
if(siz[u]-siz[v]==0)return 0;
if(l==r)return l;int mid=(l+r)>>1;
if(pos<=mid)return query1(ch[u][0],ch[v][0],l,mid,pos);
int res=query1(ch[u][1],ch[v][1],mid+1,r,pos);
if(res>0)return res;
else return query1(ch[u][0],ch[v][0],l,mid,pos);
}int query2(int u,int v,int l,int r,const double&pos){
if(siz[u]-siz[v]==0)return n+1;
if(l==r)return l;int mid=(l+r)>>1;
if(pos>mid)return query2(ch[u][1],ch[v][1],mid+1,r,pos);
int res=query2(ch[u][0],ch[v][0],l,mid,pos);
if(res<n+1)return res;
else return query2(ch[u][1],ch[v][1],mid+1,r,pos);
}struct node{int u,v;}e[N];
int nxp[N],f[N],cnt=0,rt=0,du[N];
inline void add(int u,int v){e[++cnt]=(node){u,v};nxp[cnt]=f[u];f[u]=cnt;du[u]++,du[v]++;}
int st[N],ed[N],tim=0,size[N],ans[N],mx[N],sub[N],rev[N],mn[N];
void dfs1(int u,int fa){int v;
st[u]=++tim;size[u]=1;rev[tim]=u;mn[u]=1e9;
for(int i=f[u];i;i=nxp[i]){
dfs1(v=e[i].v,u),size[u]+=size[v];
if(size[v]>mx[u])sub[u]=mx[u],mx[u]=size[v];
else if(size[v]>sub[u])sub[u]=size[v];
mn[u]=min(mn[u],size[v]);
}ed[u]=tim;
}
inline void cmax(int&x,const int&y){(x<y)&&(x=y);}
inline void cmin(int&x,const int&y){(x>y)&&(x=y);}
void dfs2(const int&u,int fa){
int x=n-size[u];
if(x>mx[u])sub[u]=mx[u],mx[u]=x;
else if(x>sub[u])sub[u]=x;
if(u!=rt)mn[u]=min(mn[u],x);
rt2[u]=add(rt2[fa],1,n,size[u]);
for(int i=f[u];i;i=nxp[i])dfs2(e[i].v,u);
if(du[u]==1)return (ans[u]=n-1),void();ans[u]=1e9;
double need=1.0*(mx[u]-mn[u])/2;int res1=0,res2=0,v;
if(mx[u]==x){
res1=query1(rt2[fa],0,1,n,need+size[u])-size[u],cmin(ans[u],max(mx[u]-res1,mn[u]+res1));
res1=query1(rt1[st[u]-1],rt2[fa],1,n,need), cmin(ans[u],max(mx[u]-res1,mn[u]+res1));
res1=query1(rt1[n],rt1[ed[u]],1,n,need), cmin(ans[u],max(mx[u]-res1,mn[u]+res1));
res2=query2(rt2[fa],0,1,n,need+size[u])-size[u],cmin(ans[u],max(mn[u]+res2,mx[u]-res2));
res2=query2(rt1[st[u]-1],rt2[fa],1,n,need), cmin(ans[u],max(mn[u]+res2,mx[u]-res2));
res2=query2(rt1[n],rt1[ed[u]],1,n,need), cmin(ans[u],max(mn[u]+res2,mx[u]-res2));
}else for(int i=f[u];i;i=nxp[i])
if(size[v=e[i].v]==mx[u]){
res1=query1(rt1[ed[v]],rt1[st[v]-1],1,n,need),cmin(ans[u],max(mx[u]-res1,mn[u]+res1));
res2=query2(rt1[ed[v]],rt1[st[v]-1],1,n,need),cmin(ans[u],max(mn[u]+res2,mx[u]-res2));
break;
}
cmax(ans[u],sub[u]);
}
int main(){n=red();
for(int re i=1;i<=n;i++){
int a=red(),b=red();
if(!a)rt=b;else add(a,b);
}dfs1(rt,0);
for(int re i=1;i<=n;i++)rt1[i]=add(rt1[i-1],1,n,size[rev[i]]);
dfs2(rt,0);for(int re i=1;i<=n;i++)cout<<ans[i]<<"\n";
}
五.平衡树
基本操作
WOJ2445 普通平衡树
WOJ2446 文艺平衡树
WOJ4521 [TJOI2019]甲苯先生的滚榜
代码:
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+5;
typedef unsigned int ui;
int a,b,l,r,p,n,m;
inline ui red(){
ui data=0;char ch=0;
ch=getchar();
while(ch<'0' || ch>'9') ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return data;
}
struct node{
int num,tim;
friend inline bool operator<(const node&a,const node&b){return a.num>b.num||(a.num==b.num&&a.tim<b.tim);}
}val[N],w[N];
int ch[N][2],siz[N],pri[N],rt;
inline void pushup(const int&u){siz[u]=siz[ch[u][0]]+siz[ch[u][1]]+1;}
int merge(int x,int y){
if(!x||!y)return x|y;
if(pri[x]<pri[y])return ch[x][1]=merge(ch[x][1],y),pushup(x),x;
else return ch[y][0]=merge(x,ch[y][0]),pushup(y),y;
}
void split(int u,const node&v,int&x,int&y){
if(!u)return(void)(x=y=0);
if(val[u]<v)x=u,split(ch[u][1],v,ch[x][1],y);
else y=u,split(ch[u][0],v,x,ch[y][0]);
pushup(u);
}
ui last=7,seed;
inline ui ran(){
seed=(seed<<4)+seed+last;
return seed%m+1;
}
int build(int l,int r){
if(l>=r)return 0;
int mid=l+r>>1;siz[mid]=1;
val[mid].num=0;val[mid].tim=0;
ch[mid][0]=0;ch[mid][1]=0;
if(l==r-1)return mid;
ch[mid][0]=build(l,mid);
ch[mid][1]=build(mid+1,r);
return pushup(mid),mid;
}
int pre=0;
inline void del(const node&v){
split(rt,v,l,r);
split(r,(node){v.num,v.tim+1},p,r);pre=p;
p=merge(ch[p][0],ch[p][1]);
rt=merge(l,merge(p,r));
}
inline int add(const node&v){
split(rt,v,l,r);
int ans=siz[l];
ch[pre][0]=0;ch[pre][1]=0;
val[pre]=v;siz[pre]=1;
rt=merge(l,merge(pre,r));
return ans;
}
inline void print(ui x){
if(x>9)print(x/10);
putchar(x%10+'0');
}
int main(){
int T_T=red();
for(int re i=414;i<=827;++i)srand(rand());
for(int re i=1;i^100001;++i)pri[i]=rand();
while(T_T--){
m=red();n=red();seed=red();
memset(w,0,sizeof(w));
rt=build(1,m+1);
while(n--){l=r=p=0;
a=ran(),b=ran();del(w[a]);
++w[a].num;w[a].tim+=b;
print(last=add(w[a]));
putchar('\n');
}
}
}
其它操作
WOJ4730 攻城略池
平衡树启发式合并。
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+5;
int n,m,a,b,c,d[N];
typedef long long ll;
ll ret=0;
inline int red(){
int data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
namespace tree{
int siz[N],ch[N][2],st[N],top=0,tot=0,fa[N];ll val[N],sum[N],q[N],num=0;
const double alpha=0.8;
inline void pushup(const int&u){
sum[u]=sum[ch[u][0]]+sum[ch[u][1]]+val[u];
siz[u]=siz[ch[u][0]]+siz[ch[u][1]]+1;
}
inline bool get(const int&u){return u==ch[fa[u]][1];}
inline int node(int v){
int u=top?st[top--]:++tot;fa[u]=0;
ch[u][0]=ch[u][1]=0;sum[u]=val[u]=v;
siz[u]=1;return u;
}
inline bool check(const int&u){return max(siz[ch[u][0]],siz[ch[u][1]])>=siz[u]*alpha;}
void collect(int u){
if(ch[u][0])collect(ch[u][0]);
q[++num]=val[u];st[++top]=u;
if(ch[u][1])collect(ch[u][1]);
}
int build(int l,int r){
if(l>=r)return 0;
int mid=(l+r)>>1;
int u=node(q[mid]);
ch[u][0]=build(l,mid);
ch[u][1]=build(mid+1,r);
fa[ch[u][0]]=fa[ch[u][1]]=u;
return pushup(u),u;
}
inline void rebuild(int&u){
int ff=fa[u];
num=0;collect(u);
fa[u=build(1,num+1)]=ff;
}
int insert(int &u,const int&v){
if(!u)return u=node(v),0;
int d=(v>val[u]),res=insert(ch[u][d],v);
fa[ch[u][d]]=u;pushup(u);
if(check(u))res=u;return res;
}void ins(int &u,const int&v){
int res=insert(u,v);
if(!res)return;
if(res==u)rebuild(u);
else rebuild(ch[fa[res]][get(res)]);
}void join(int &u,int v,ll w){num=0;collect(v);
for(int re i=num;i;--i)ins(u,q[i]+w);
}
ll query(int u,int&size,ll&sm,const int need){if(!u)return -100000;
if(val[u]*(size+siz[ch[u][0]]+1)-sm-val[u]-sum[ch[u][0]]<=need){
size+=siz[ch[u][0]]+1,sm+=val[u]+sum[ch[u][0]];
return max(query(ch[u][1],size,sm,need),val[u]);
}else return query(ch[u][0],size,sm,need);
}
}
using tree::join;
struct node{int v,w;}e[N<<1|1];
int f[N],nxp[N<<1|1],cnt=0;
inline void add(const int&u,const int&v,const int&w){
e[++cnt]=(node){v,w};nxp[cnt]=f[u];f[u]=cnt;
e[++cnt]=(node){u,w};nxp[cnt]=f[v];f[v]=cnt;
}
namespace TCP{
int siz[N],son[N],tag[N],rt[N];
void dfs1(int u,int fa){
siz[u]=1;int v;
for(int re i=f[u];i;i=nxp[i]){
if((v=e[i].v)==fa)continue;
dfs1(v,u);siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v,tag[u]=e[i].w;
}
}
void solve(int u){
if(!rt[u])tree::ins(rt[u],0);
else{int siz=0;ll ans=0,sum=0;
ans=tree::query(rt[u],siz,sum,d[u]);
ll res=d[u]-(ans*siz-sum);
if(res)ans+=(res-1)/siz+1;
ret=max(ret,ans+tag[u]);tree::ins(rt[u],ans);
}
}
void dfs2(int u,int fa){int v;
if(son[u])dfs2(son[u],u),rt[u]=rt[son[u]],tag[u]+=tag[son[u]];
for(int re i=f[u];i;i=nxp[i]){
if((v=e[i].v)==fa||v==son[u])continue;
dfs2(v,u);join(rt[u],rt[v],tag[v]+e[i].w-tag[u]);
}solve(u);
}
}
using TCP::dfs1;
using TCP::dfs2;
int main(){n=red();
for(int re i=1;i<=n;i++)d[i]=red();
for(int re i=1;i<n;i++){
a=red(),b=red(),c=red();
add(a,b,c);
}dfs1(1,0);dfs2(1,0);
cout<<ret<<"\n";
return 0;
}
WOJ4735 Phorni
后缀平衡树模板。
#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e6+5;
int n,m,a[N],b,c,len;
typedef long long ll;
ll ret=0;
inline int red(){
int data=0;bool w=0; char ch=getchar();
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=1,ch=getchar();
while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
return w?-data:data;
}
char s[N];
int las=0;
namespace tree{
int siz[N],ch[N][2],rt=0,*need;
double ll[N],rr[N],val[N];
const double alpha=0.75;
inline bool cmp(const int a,const int b){
if(s[a]!=s[b])return s[a]<s[b];
return val[a-1]<val[b-1];
}
inline bool check(const int&u){return max(siz[ch[u][0]],siz[ch[u][1]])>=siz[u]*alpha;}
void collect(int u,vector<int>&v){
if(ch[u][0])collect(ch[u][0],v);
v.push_back(u);
if(ch[u][1])collect(ch[u][1],v);
}
int build(int l,int r,vector<int>&v,double ls,double rs){
if(l>=r)return 0;
int mid=(l+r)>>1;
int u=v[mid];siz[u]=r-l;
val[u]=ls+rs;ll[u]=ls;rr[u]=rs;
ch[u][0]=build(l,mid,v,ls,val[u]/2);
ch[u][1]=build(mid+1,r,v,val[u]/2,rs);
return u;
}
inline void rebuild(int&u){
static vector<int>v;v.clear();
collect(u,v);u=build(0,v.size(),v,ll[u],rr[u]);
}
void insert(int &u,const int&v,const double l,const double r){
if(!u)return (void)(u=v,ll[u]=l,rr[u]=r,siz[u]=1,val[u]=l+r);
++siz[u];double mid=(l+r)/2;
if(cmp(v,u))insert(ch[u][0],v,l,mid);
else insert(ch[u][1],v,mid,r);
if(check(u))need=&u;
}void ins(const int&v){
need=NULL;insert(rt,v,-1e9,1e9);
if(need!=NULL)rebuild(*need);
}
}
using tree::val;
namespace sgt{
#define lc (p<<1)
#define rc (p<<1|1)
int mx[N<<1|1];
inline bool cmp(const int&x,const int&y){return a[x]==a[y]?x<y:val[a[x]]<val[a[y]];}
inline void pushup(const int&p){mx[p]=cmp(mx[lc],mx[rc])?mx[lc]:mx[rc];}
void build(const int p,const int l,const int r){
if(l==r)return mx[p]=l,void();
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(p);
}
void change(int p,int l,int r,int pos){
if(l==r)return;int mid=(l+r)>>1;
if(pos<=mid)change(lc,l,mid,pos);
else change(rc,mid+1,r,pos);
pushup(p);
}
int query(int p,int l,int r,int ql,int qr){
if(ql<=l&&qr>=r)return mx[p];
int mid=(l+r)>>1;
if(qr<=mid)return query(lc,l,mid,ql,qr);
if(ql>mid)return query(rc,mid+1,r,ql,qr);
int p1=query(lc,l,mid,ql,qr),p2=query(rc,mid+1,r,ql,qr);
return cmp(p1,p2)?p1:p2;
}
}
int main(){
n=red();m=red();len=red();
scanf("%s",s+1);reverse(s+1,s+len+1);
for(int re i=1;i<=len;i++)tree::ins(i);
for(int re i=1;i<=n;i++)a[i]=red();
sgt::build(1,1,n);
while(m--){
char op=getchar();
while(op!='I'&&op!='Q'&&op!='C')op=getchar();
if(op=='I'){
s[++len]=(red()^las)+'a';
tree::ins(len);
}if(op=='C'){
int pos=red();a[pos]=red();
sgt::change(1,1,n,pos);
}if(op=='Q'){
int l=red(),r=red();
cout<<(las=sgt::query(1,1,n,l,r))<<"\n";
}
}
}