《zkw 线段树-原理及其扩展》例题
《zkw 线段树-原理及其扩展》例题
我的博客 Tmbcan's Blog
非常简单的模板题没有写讲解,难一些的写了讲解或者注意事项。
难度大致由易到难。
例一:Luogu P3372 【模板】线段树 1
const int N = 1e5+10;
int n,m,P=1;
ll tr[N*3],sum[N*3];
inline void change_add(int l,int r,ll k){
int siz = 1;
for(l=P+l-1,r=P+r+1;l^1^r; ){
if(~l&1) tr[l^1]+=siz*k,sum[l^1]+=k;
if(r&1) tr[r^1]+=siz*k,sum[r^1]+=k;
l>>=1; r>>=1; siz<<=1;
tr[l] = tr[l<<1]+tr[l<<1|1]+sum[l]*siz;
tr[r] = tr[r<<1]+tr[r<<1|1]+sum[r]*siz;
}
for(l>>=1,siz<<=1; l ;l>>=1,siz<<=1) tr[l] = tr[l<<1]+tr[l<<1|1]+sum[l]*siz;
}
inline ll query(int l,int r){
ll res = 0;
int sizl = 0,sizr = 0,siz = 1;
for(l=l+P-1,r=r+P+1;l^1^r; ){
if(~l&1) res+=tr[l^1],sizl+=siz;
if(r&1) res+=tr[r^1],sizr+=siz;
l>>=1; r>>=1; siz<<=1;
res += sum[l]*sizl+sum[r]*sizr;
}
for(l>>=1,sizl+=sizr;l;l>>=1) res+=sum[l]*sizl;
return res;
}
int main(){
read(n,m);
while(P<=n+1) P<<=1;
for(int i=1;i<=n;++i) read(tr[P+i]);
for(int i=P-1;i;--i) tr[i] = tr[i<<1|1]+tr[i<<1];
int opt,l,r;ll k;
while(m--){
read(opt,l,r);
if(opt==1){
read(k);
change_add(l,r,k);
}
else{
printf("%lld\n",query(l,r));
}
}
return 0;
}
例二:Luogu P3373 【模板】线段树 2
注意下放标记时“加”和“乘”标记的下放顺序。
const int N = 1e5+10;
int n,m,P=1,DEP=0;
ll tr[N*3],sum[N*3],mul[N*3],mod;
inline void push_down(int p,ll siz){
siz >>= 1;
(tr[p<<1] = tr[p<<1]*mul[p]+sum[p]*siz)%=mod;
(sum[p<<1] = sum[p<<1]*mul[p]+sum[p])%=mod;
(mul[p<<1] *= mul[p])%=mod;
(tr[p<<1|1] = tr[p<<1|1]*mul[p]+sum[p]*siz)%=mod;
(sum[p<<1|1] = sum[p<<1|1]*mul[p]+sum[p])%=mod;
(mul[p<<1|1] *= mul[p])%=mod;
mul[p] = 1;sum[p] = 0;
}
inline void change_add(int l,int r,ll k){
l += P-1; r += P+1;
for(int i=DEP;i;--i) push_down(l>>i,1<<i),push_down(r>>i,1<<i);
int siz = 1;
while(l^1^r){
if(~l&1) (tr[l^1]+=k*siz)%=mod,(sum[l^1]+=k)%=mod;
if(r&1) (tr[r^1]+=k*siz)%=mod,(sum[r^1]+=k)%=mod;
l>>=1; r>>=1; siz<<=1;
(tr[l] = tr[l<<1]+tr[l<<1|1])%=mod;
(tr[r] = tr[r<<1]+tr[r<<1|1])%=mod;
}
for(l>>=1; l ;l>>=1) (tr[l] = tr[l<<1]+tr[l<<1|1])%=mod;
}
inline void change_mul(int l,int r,ll k){
l += P-1; r += P+1;
for(int i=DEP;i;--i) push_down(l>>i,1<<i),push_down(r>>i,1<<i);
while(l^1^r){
if(~l&1) (tr[l^1]*=k)%=mod,(mul[l^1]*=k)%=mod,(sum[l^1]*=k)%=mod;
if(r&1) (tr[r^1]*=k)%=mod,(mul[r^1]*=k)%=mod,(sum[r^1]*=k)%=mod;
l>>=1; r>>=1;
(tr[l] = tr[l<<1]+tr[l<<1|1])%=mod;
(tr[r] = tr[r<<1]+tr[r<<1|1])%=mod;
}
for(l>>=1; l ;l>>=1) (tr[l] = tr[l<<1]+tr[l<<1|1])%=mod;
}
inline ll query(int l,int r){
l += P-1; r += P+1;
for(int i=DEP;i;--i) push_down(l>>i,1<<i),push_down(r>>i,1<<i);
ll res = 0;
while(l^1^r){
if(~l&1) (res+=tr[l^1])%=mod;
if(r&1) (res+=tr[r^1])%=mod;
l>>=1; r>>=1;
}
return res%mod;
}
int main(){
read(n,m,mod);
while(P<=n+1) P<<=1,++DEP;
for(int i=1;i<=n;++i){
read(tr[P+i]); tr[P+i]%=mod;
mul[P+i] = 1;//sum[P+i] = 0;
}
for(int i=P-1;i;--i){
(tr[i] = tr[i<<1|1]+tr[i<<1])%=mod;
mul[i] = 1;//sum[i] = 0;
}
int opt,l,r;ll k;
while(m--){
read(opt,l,r);
if(opt==1){
read(k);
change_mul(l,r,k);
}
else if(opt==2){
read(k);
change_add(l,r,k);
}
else printf("%lld\n",query(l,r));
}
return 0;
}
例三:Luogu P3919 【模板】可持久化线段树 1(可持久化数组)
离散化后,输出别输出成离散化数据,记得输出原数。
const int N = 3e7+10,M = 1e6+10;
int P = 1,DEP = 0,NOW;
int tr[N],son[N][2],rt[M];
inline void update(int i,int vi,int val,int l){
int tl = l+P;
int v = rt[vi];
rt[i] = l = ++NOW;
for(int dep=DEP-1; dep>=0 ;--dep){
if((tl>>dep)&1) son[l][0] = son[v][0],son[l][1] = ++NOW,v = son[v][1];
else son[l][0] = ++NOW,son[l][1] = son[v][1],v = son[v][0];
l = NOW;
}
tr[l] = val;
}
inline int query(int i,int vi,int l){
rt[i] = rt[vi];
int tl = l+P;
l = rt[vi];
for(int dep=DEP-1; dep>=0 ;--dep){
if((tl>>dep)&1) l=son[l][1];
else l = son[l][0];
}
return tr[l];
}
int main(){
int n,T;read(n,T);
while(P<=n+1) P<<=1,++DEP; NOW = (1<<(DEP+1))-1;
for(int i=1;i<=n;++i) read(tr[P+i]); rt[0] = 1;
for(int i=P-1;i;--i) son[i][0]=i<<1,son[i][1]=i<<1|1;
for(int i=1,opt,vi,loc,val;i<=T;++i){
read(vi,opt,loc);
if(opt==1){
read(val);
update(i,vi,val,loc);
}
else printf("%d\n",query(i,vi,loc));
}
return 0;
}
例四:Luogu P3834 【模板】可持久化线段树 2
因为有人说 zkw 做不了主席树,我直接急了,所以必须得放一个主席树模板题证明自己。
主席树、划分树、归并树都可以做,给出的代码实现的是主席树。
注意先离散化数据,最后输出的时候输出原数据。
const int N = 2e7+10,M = 2e5+10;
int P = 1,DEP = 0,NOW;
int tr[N],son[N][2],rt[M],a[M],b[M],to[N];
inline void update(int i,int vi,int l,int k){
//新建节点,与可持久化线段树相同
int tl = l+P;
int v = rt[vi];
rt[i] = l = ++NOW;
for(int dep=DEP-1; dep>=0 ;--dep,l=NOW){
tr[l] = tr[v]+k;//更新当前节点信息
if((tl>>dep)&1) son[l][0] = son[v][0],son[l][1] = ++NOW,v = son[v][1];
else son[l][1] = son[v][1],son[l][0] = ++NOW,v = son[v][0];
}
tr[l] = tr[v]+k;//最后到叶子节点
to[l] = tl;//记录当前节点对应在初始树上的节点
}
inline int query(int l,int r,int k){
//查询第 k 大,与权值线段树相同
l = rt[l];r = rt[r];
for(int dep=0;dep<DEP;++dep){
//左右子树选举模拟二分过程
int num = tr[son[r][0]]-tr[son[l][0]];
if(num>=k){
l = son[l][0];
r = son[r][0];
}
else{
k -= num;
l = son[l][1];
r = son[r][1];
}
}
return to[l]-P;
}
int main(){
int n,T;read(n,T);
for(int i=1;i<=n;++i) read(a[i]),b[i]=a[i];
sort(b+1,b+1+n);
int idx = unique(b+1,b+1+n)-(b+1);
while(P<=idx+1) P<<=1,++DEP;
NOW = (1<<(DEP+1))-1; rt[0] = 1;//建初始线段树
for(int i=P-1;i;--i) son[i][0]=i<<1,son[i][1]=i<<1|1;
for(int i=1;i<=n;++i) to[i+P]=i+P;
for(int i=1,k;i<=n;++i){
k = lower_bound(b+1,b+idx+1,a[i])-b;
update(i,i-1,k,1);//插入离散化后的值
}
for(int i=1,l,r,k;i<=T;++i){
read(l,r,k);
printf("%d\n",b[query(l-1,r,k)]);
}
return 0;
}
例五:Luogu P3369 【模板】普通平衡树
本题有很多种解法,代码实现的是权值线段树。
const int N = 1e5+10;
int id[N],idx;
struct Query{
int opt,v;
}q[N];
int n;
int P = 1,DEP = 0,tr[N*3];
inline void update(int l,int k){
l += P;
tr[l] += k;
for(l>>=1; l ;l>>=1) tr[l] = tr[l<<1]+tr[l<<1|1];
}
inline int query(int l,int r){
l += P-1; r += P+1;
int res = 0;
while(l^1^r){
if(~l&1) res+=tr[l^1];
if(r&1) res+=tr[r^1];
l>>=1; r>>=1;
}
return res;
}
inline int get_rank(int k){
int dep = 0,l = 1;
while(dep<DEP){
if(k<=tr[l<<1]) l=l<<1;
else k-=tr[l<<1],l=l<<1|1;
++dep;
}
return l-P-1;
}
int main(){
read(n);
for(int i=1;i<=n;++i){
read(q[i].opt,q[i].v);
if(q[i].opt!=4) id[++idx] = q[i].v;
}
sort(id+1,id+idx+1);
idx = unique(id+1,id+idx+1)-(id+1);
while(P<=idx+1) P<<=1,++DEP;
for(int i=1,k,rk;i<=n;++i){
k = lower_bound(id+1,id+idx+1,q[i].v)-id;
if(q[i].opt==1) update(k,1);
else if(q[i].opt==2) update(k,-1);
else if(q[i].opt==3) printf("%d\n",(k==1) ? k : (query(1,k-1)+1));
else if(q[i].opt==4) printf("%d\n",id[get_rank(q[i].v)-1]);
else if(q[i].opt==5){
rk = query(1,k-1);
printf("%d\n",id[get_rank(rk)-1]);
}
else if(q[i].opt==6){
rk = query(1,k);
printf("%d\n",id[get_rank(rk+1)-1]);
}
}
return 0;
}
例六:Luogu P3380 【模板】树套树
本题经典写法是线段树套平衡树。给出代码实现的是树状数组套权值线段树。
线段树前缀和维护全局第 \(k\) 大,树状数组维护线段树的前缀和。每次用 \(lowbit\) 求出要修改和查询的线段树,然后整体修改查询即可。
注意求前驱和后继,与权值线段树的原理相同,结合 \(get\_rank\) 和 \(get\_num\) 即可。
const int N = 1e5+10,LG = 17,INF = 2147483647;
int n,m,a[N],b[N],idx;
int tmp[N][2],tmp0,tmp1;
struct Query{
int opt,l,r,k;
}q[N];
// #define ls(x) (x<<1)
// #define rs(x) (x<<1|1)
int P=1,DEP=0,NOW;
int tr[N*LG*LG],son[N*LG*LG][2],rt[N];//计算好空间
inline void update(int i,int l,int k){
int tl = l+P;
rt[i] ? 0 : rt[i]=++NOW;//动态开点
l = rt[i];
for(int dep=DEP-1;dep>=0;--dep){
tr[l] += k;
if((tl>>dep)&1) son[l][1]?0:son[l][1]=++NOW,l=son[l][1];
else son[l][0]?0:son[l][0]=++NOW,l=son[l][0];
}
tr[l] += k;
}
inline int query_rank(int l){
l += P;
int res = 0;
for(int dep=DEP-1;dep>=0;--dep){
if((l>>dep)&1){//前缀和
for(int i=1;i<=tmp0;++i) res-=tr[son[tmp[i][0]][0]],tmp[i][0]=son[tmp[i][0]][1];
for(int i=1;i<=tmp1;++i) res+=tr[son[tmp[i][1]][0]],tmp[i][1]=son[tmp[i][1]][1];
}
else{
for(int i=1;i<=tmp0;++i) tmp[i][0]=son[tmp[i][0]][0];
for(int i=1;i<=tmp1;++i) tmp[i][1]=son[tmp[i][1]][0];
}
}
return res;
}
inline int query_num(int k){
int l = 1;
for(int dep=0,res=0;dep<DEP;++dep,res=0){
for(int i=1;i<=tmp0;++i) res-=tr[son[tmp[i][0]][0]];
for(int i=1;i<=tmp1;++i) res+=tr[son[tmp[i][1]][0]];
if(k>res){//线段树上二分
k -= res;
for(int i=1;i<=tmp0;++i) tmp[i][0]=son[tmp[i][0]][1];
for(int i=1;i<=tmp1;++i) tmp[i][1]=son[tmp[i][1]][1];
l = l<<1|1;
}
else{
for(int i=1;i<=tmp0;++i) tmp[i][0]=son[tmp[i][0]][0];
for(int i=1;i<=tmp1;++i) tmp[i][1]=son[tmp[i][1]][0];
l = l<<1;
}
}
return l-P;
}
inline int get_rank(int l,int r,int k){
tmp0 = tmp1 = 0;
for(int i=l-1; i ;i-=(i&-i)) tmp[++tmp0][0] = rt[i];
for(int i=r; i ;i-=(i&-i)) tmp[++tmp1][1] = rt[i];
return query_rank(k)+1;
}
inline int get_num(int l,int r,int k){
tmp0 = tmp1 = 0;
for(int i=l-1; i ;i-=(i&-i)) tmp[++tmp0][0] = rt[i];
for(int i=r; i ;i-=(i&-i)) tmp[++tmp1][1] = rt[i];
return query_num(k);
}
inline int get_pre(int l,int r,int k){
int rk = get_rank(l,r,k)-1;
if(rk==0) return 0;//第一个数没前驱
return get_num(l,r,rk);
}
inline int get_nex(int l,int r,int k){
if(k>=idx) return idx+1;//最后一个数没后继
int rk = get_rank(l,r,k+1);
if(rk==r-l+2) return idx+1;//最后一个数
return get_num(l,r,rk);
}
// #define lowbit(x) (x&-x)
inline void add(int x,int k){
for(int i=x;i<=n;i+=(i&-i)) update(i,a[x],k);//求需要哪个线段树
}
int main(){
read(n,m);
for(int i=1;i<=n;++i){
read(a[i]);
b[++idx] = a[i];
}
for(int i=1;i<=m;++i){
read(q[i].opt);
if(q[i].opt==3) read(q[i].l,q[i].k);
else read(q[i].l,q[i].r,q[i].k);
if(q[i].opt==4 || q[i].opt==5 || q[i].opt==3) b[++idx] = q[i].k;
}
sort(b+1,b+1+idx);
idx = unique(b+1,b+1+idx)-(b+1);
while(P<=idx+1) P<<=1,++DEP;
for(int i=1;i<=n;++i){
a[i] = lower_bound(b+1,b+1+idx,a[i])-b;
add(i,1);
}
b[0] = -INF; b[idx+1] = INF;
for(int i=1;i<=m;++i){
if(q[i].opt!=2) q[i].k = lower_bound(b+1,b+1+idx,q[i].k)-b;
if(q[i].opt==1) printf("%d\n",get_rank(q[i].l,q[i].r,q[i].k));
else if(q[i].opt==2) printf("%d\n",b[get_num(q[i].l,q[i].r,q[i].k)]);
else if(q[i].opt==3){
add(q[i].l,-1);
a[q[i].l] = q[i].k;
add(q[i].l,1);
}
else if(q[i].opt==4) printf("%d\n",b[get_pre(q[i].l,q[i].r,q[i].k)]);
else if(q[i].opt==5) printf("%d\n",b[get_nex(q[i].l,q[i].r,q[i].k)]);
}
return 0;
}
例七:Luogu P3157 [CQOI2011] 动态逆序对
对答案有贡献的点对应满足:
- 删除时间 \(tim_i<tim_j\);
- 所在位置 \(pos_i<pos_j\);
- 数字权值 \(val_i>val_j\)。
此时题目就变成了三维偏序问题,经典的三维偏序离线做法是 CDQ 分治,在线做法可以用树套树实现。代码实现的是树状数组套线段树。
静态求逆序对时,我们可以使用树状数组。删除数据时,逆序对减少数量为前面的比当前数字大的数的个数以及后面的比当前数字小的数的个数。
我们先按删除时间排序消去第一维。为了维护数的权值和位置,我们再对每一个树状数组的节点建一棵权值线段树进行维护。
const int N = 1e5+10,LG = 18;
int n,m,a[N],idx[N],st[N];ll ans;
int tmp[N][2],tmp0,tmp1;
// #define ls(x) (x<<1)
// #define rs(x) (x<<1|1)
int P=1,DEP=0,NOW;
int tr[3*N+N*LG*LG],son[3*N+N*LG*LG][2],rt[N];
inline void update(int i,int l,int k){
int tl = l+P,stop = 0;
rt[i] ? 0 : rt[i]=++NOW;
l = rt[i]; st[++stop] = l;
for(int dep=DEP-1;dep>=0;--dep,st[++stop]=l){
if((tl>>dep)&1) son[l][1]?0:son[l][1]=++NOW,l=son[l][1];
else son[l][0]?0:son[l][0]=++NOW,l=son[l][0];
}
tr[l] += k;
while(--stop) tr[st[stop]] = tr[son[st[stop]][0]]+tr[son[st[stop]][1]];
}
inline ll query(int x,int y,int k,int f){
tmp0 = tmp1 = 0;
for(int i=x-1; i ;i-=(i&-i)) tmp[++tmp0][0]=rt[i];
for(int i=y; i ;i-=(i&-i)) tmp[++tmp1][1]=rt[i];
ll res = 0;
for(int dep=DEP-1,l=k+P;dep>=0;--dep){
if((l>>dep)&1){
if(f){
for(int i=1;i<=tmp0;++i) res-=tr[son[tmp[i][0]][0]];
for(int i=1;i<=tmp1;++i) res+=tr[son[tmp[i][1]][0]];
}
for(int i=1;i<=tmp0;++i) tmp[i][0]=son[tmp[i][0]][1];
for(int i=1;i<=tmp1;++i) tmp[i][1]=son[tmp[i][1]][1];
}
else{
if(!f){
for(int i=1;i<=tmp0;++i) res-=tr[son[tmp[i][0]][1]];
for(int i=1;i<=tmp1;++i) res+=tr[son[tmp[i][1]][1]];
}
for(int i=1;i<=tmp0;++i) tmp[i][0]=son[tmp[i][0]][0];
for(int i=1;i<=tmp1;++i) tmp[i][1]=son[tmp[i][1]][0];
}
}
return res;
}
// #define lowbit(x) (x&-x)
inline void add(int x,int k){
for(int i=x;i<=n;i+=(i&-i)) update(i,a[x],k);
}
int main(){
read(n,m);
while(P<=n+1) P<<=1,++DEP;
NOW = (1<<(DEP+1))-1;
for(int i=1;i<=n;++i){
read(a[i]);
idx[a[i]] = i;
ans += query(1,i-1,a[i],0);
add(i,1);
}
printf("%lld\n",ans);
for(int i=1,x;i<m;++i){
read(x);
ans -= query(1,idx[x]-1,x,0);
ans -= query(idx[x]+1,n,x,1);
printf("%lld\n",ans);
add(idx[x],-1);
}
return 0;
}
例八:Luogu P4198 楼房重建
兔队线段树,要求实现单点修改、动态维护最长上升子序列长度。本人题解。
根据生活经验,我们需要维护每栋楼房最高点到原点连线的斜率 \(k\) 的最长上升子序列。
显然地,对于一个区间,我们所选的起始楼房越靠左侧越优。所以在合并信息时,左区间答案始终不变,对右区间进行二分查找第一个大于左区间最大值的值。
所以我们需要记录每个区间的 \(k\) 的最大值,并且在查找右区间的过程中:
- 如果右区间最大值都比左区间最大值小了,那右区间对答案没有贡献;
- 如果右区间对答案有贡献,就继续向下查找;
- 当找到叶子节点时,看节点值是否大于左区间最大值。
先修改信息,然后在 zkw 线段树上动态模拟选点维护过程即可。
const int N = 1e5+10;
int n,m;
int P = 1,DEP = 0;
double mx[N*3];int len[N*3];
inline int query(int l,double k,int dep){
int tl = l;
int res = 0;
while(dep<DEP){
if(mx[l]<=k) break;//剪枝:右区间最大值比左区间的小
l = l<<1|(mx[l<<1]<=k);//模拟选取左右子树过程
dep++;
}
if(dep==DEP) res = (mx[l]>k);//叶子节点
while(l!=tl){
if(~l&1) l>>=1,res+=len[l]-len[l<<1];//累加答案
else l>>=1;
}
return res;
}
inline void update(int l,double k){
l += P;
//直接对叶子节点进行更新
len[l] = 1;mx[l] = k;
int dep = DEP;
//从下到上维护父子结点关系
for(l>>=1,--dep; l ;l>>=1,--dep){
mx[l] = max(mx[l<<1],mx[l<<1|1]);
len[l] = len[l<<1]+query(l<<1|1,mx[l<<1],dep+1);
}
}
int main(){
read(n,m);
while(P<=n+1) P<<=1,++DEP;
int l,r;
while(m--){
read(l,r);
update(l,(1.0*r)/(1.0*l));
printf("%d\n",len[1]);//输出根节点信息
}
return 0;
}
例九:CF 786B Legacy
线段树优化建图模板题。
首先建出入树和出树,然后跑最短路即可。注意连边时从入树向出树连。
const int N = 1e5+10,LG = 20;
int n,q,s; ll INF;
struct{
int to,nex; ll w;
}edge[N*LG*2];
int head[N*LG*2],edge_num;
inline void add(int x,int y,ll w){
edge[++edge_num].to = y;
edge[edge_num].nex = head[x];
head[x] = edge_num;
edge[edge_num].w = w;
}
int P=1,DEP=0,NOW;
inline void update_dot_intvl(int v,int l,int r,ll w){
l += P-1; r += P+1; v += P;
while(l^1^r){
if(~l&1) add(v+NOW,l^1,w);
if(r&1) add(v+NOW,r^1,w);
l>>=1;r>>=1;
}
}
inline void update_intvl_dot(int v,int l,int r,ll w){
l += P-1; r += P+1; v += P;
while(l^1^r){
if(~l&1) add((l^1)+NOW,v,w);
if(r&1) add((r^1)+NOW,v,w);
l>>=1;r>>=1;
}
}
ll dis[N*6];
int vis[N*6];
struct node{
ll w; int p;
bool operator < (const node&G) const{
return w > G.w;
}
};
inline void dijs(){
s += P+NOW;
memset(dis,0x3f,sizeof(dis));
INF = dis[s]; dis[s] = 0;
priority_queue <node> q;
q.push({0,s});
while(!q.empty()){
node now = q.top();
q.pop();
if(vis[now.p]) continue;
vis[now.p] = 1;
for(int i=head[now.p];i;i=edge[i].nex){
int tto = edge[i].to;
if(dis[tto]>dis[now.p]+edge[i].w){
dis[tto] = dis[now.p]+edge[i].w;
q.push({dis[tto],tto});
}
}
}
}
int main(){
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
read(n,q,s);
while(P<=n+1) P<<=1,++DEP;
NOW = (1<<(DEP+1))-1;//偏移量
for(int i=P-1;i;--i){
add(i,i<<1,0),add((i<<1)+NOW,i+NOW,0);
add(i,i<<1|1,0),add((i<<1|1)+NOW,i+NOW,0);
}
for(int i=1;i<=n;++i) add(i+P,i+P+NOW,0),add(i+P+NOW,i+P,0);
for(int i=1,opt,v,l,r,w;i<=q;++i){
read(opt,v);
if(opt==1){
read(l,w);
add(v+P+NOW,l+P,1ll*w);
}
else if(opt==2){
read(l,r,w);
update_dot_intvl(v,l,r,1ll*w);
}
else{
read(l,r,w);
update_intvl_dot(v,l,r,1ll*w);
}
}
dijs();
for(int i=1;i<=n;++i) printf("%lld ",dis[i+P]==INF ? -1 : dis[i+P]);
fclose(stdin);
fclose(stdout);
return 0;
}
例十:Luogu P5490 【模板】扫描线 & 矩形面积并
const int N = 1e5+100;
int n,b[N<<1],idx,lint; ll ans;
struct Line{
int y,x1,x2,k;
bool operator < (const Line&G) const{
return y < G.y;
}
}line[N<<1];
int P=1,DEP=0;
struct{
ll len; int vis;
}tr[N*6];
inline void push_up(int p,int dep){
if(tr[p].vis){
int l = p<<dep;
int r = l+(1<<dep);
tr[p].len = b[r-P]-b[l-P];
}
else{
if(P>p) tr[p].len = tr[p<<1].len+tr[p<<1|1].len;
else tr[p].len = 0;
}
}
inline void update(int l,int r,int k){
l += P-1; r += P+1; int dep = 0;
if(l>=r) return ;
while(l^1^r){
if(~l&1) tr[l^1].vis+=k,push_up(l^1,dep);
if(r&1) tr[r^1].vis+=k,push_up(r^1,dep);
l>>=1;r>>=1;++dep;
push_up(l,dep);push_up(r,dep);
}
for(l>>=1,++dep; l ;l>>=1,++dep) push_up(l,dep);
}
int main(){
read(n);
for(int i=1,x1,x2,y1,y2;i<=n;++i){
read(x1,y1,x2,y2);
b[++idx] = x1; b[++idx] = x2;
line[++lint] = {y1,x1,x2,1};
line[++lint] = {y2,x1,x2,-1};
}
sort(line+1,line+1+lint);
sort(b+1,b+1+idx);
idx = unique(b+1,b+1+idx)-(b+1);
while(P<=idx+1) P<<=1,++DEP;
for(int i=1;i<=lint;++i){
int l = lower_bound(b+1,b+1+idx,line[i].x1)-b;
int r = lower_bound(b+1,b+1+idx,line[i].x2)-b;
update(l,r-1,line[i].k);
ans += tr[1].len*(1ll*line[i+1].y-1ll*line[i].y);
}
printf("%lld",ans);
return 0;
}
例十一:Luogu P4556 【模板】线段树合并/ [Vani有约会] 雨天的尾巴
考虑树上差分,对每个点建一棵权值线段树,查询时合并线段树即可。
const int N = 1e5+10,M = 1e5,LG = 17;
int n,m;
struct{
int to,nex;
}edge[N<<1];
int head[N],edge_num;
inline void add(int x,int y){
edge[++edge_num].to = y;
edge[edge_num].nex = head[x];
head[x] = edge_num;
}
struct Pair{
int p1,p2;
Pair(){
p1 = p2 = 0;
}
Pair(int tp1,int tp2){
p1 = tp1; p2 = tp2;
}
}st[N*3];
int P=1,DEP=0,NOW;
struct Tree{
int val,pos;
Tree(){
val = pos = 0;
}
Tree(int tval,int tpos){
val = tval; pos = tpos;
}
inline Tree operator + (const Tree&G) const{
if(val>=G.val) return (*this);
else return G;
}
inline void operator += (const Pair&G){
val += G.p1;
pos = G.p2;
}
}tr[N*3*LG];
int son[N*3*LG][2],rt[N];
inline void update(int l,int v,int k){
int tl = v+P;
rt[l]?0:rt[l]=++NOW;
int tail = 0;
l = rt[l]; st[++tail].p1 = l;
for(int dep=DEP-1;dep>=0;--dep,st[++tail].p1=l){
if((tl>>dep)&1) son[l][1]?0:son[l][1]=++NOW,l=son[l][1];
else son[l][0]?0:son[l][0]=++NOW,l=son[l][0];
}
tr[l] += Pair(k,v);
while(--tail){ tr[st[tail].p1] = tr[son[st[tail].p1][0]]+tr[son[st[tail].p1][1]]; }
}
inline void merge(int l,int v){
if(!rt[l] || !rt[v]){
rt[l] = rt[l]|rt[v];
return ;
}
l = rt[l]; v = rt[v];
int head = 1,tail = 0,ttail;
st[++tail] = Pair(l,v);
for(int dep=DEP-1;dep>=0;--dep){
ttail = tail;
for(int j=tail;j>=head;--j){
l = st[j].p1; v = st[j].p2;
if(son[l][0] && son[v][0]) st[++tail] = Pair(son[l][0],son[v][0]);
else if(son[v][0]) son[l][0] = son[v][0];
if(son[l][1] && son[v][1]) st[++tail] = Pair(son[l][1],son[v][1]);
else if(son[v][1]) son[l][1] = son[v][1];
}
head = ttail+1;
}
while(tail>=head){
tr[st[tail].p1].val += tr[st[tail].p2].val;
--tail;
}
while(tail){
tr[st[tail].p1] = tr[son[st[tail].p1][0]]+tr[son[st[tail].p1][1]];
--tail;
}
}
int dep[N],fa[N][20],ans[N];
inline void dfs(int now,int fu){
dep[now] = dep[fu]+1;
fa[now][0] = fu;
for(int i=1;(1<<i)<=dep[now];++i) fa[now][i] = fa[fa[now][i-1]][i-1];
for(int i=head[now];i;i=edge[i].nex){
int tto = edge[i].to;
if(tto==fu) continue;
dfs(tto,now);
}
}
inline void get_ans(int now,int fu){
for(int i=head[now];i;i=edge[i].nex){
int tto = edge[i].to;
if(tto==fu) continue;
get_ans(tto,now);
merge(now,tto);
}
ans[now] = tr[rt[now]].pos;
if(!tr[rt[now]].val) ans[now] = 0;
}
inline int lca(int x,int y){
if(dep[x]>dep[y]) swap(x,y);
for(int i=19;i>=0;--i){
if(dep[fa[y][i]]>=dep[x]) y = fa[y][i];
}
if(x==y) return x;
for(int i=19;i>=0;--i){
if(fa[y][i]!=fa[x][i]){
y = fa[y][i];
x = fa[x][i];
}
}
return fa[x][0];
}
int main(){
read(n,m);
for(int i=1,u,v;i<n;++i){
read(u,v);
add(u,v);add(v,u);
}
dfs(1,0);
while(P<=M+1) P<<=1,++DEP;
for(int i=1,x,y,z,t;i<=m;++i){
read(x,y,z);
t = lca(x,y);
update(x,z,1);
update(y,z,1);
update(t,z,-1);
update(fa[t][0],z,-1);
}
get_ans(1,0);
for(int i=1;i<=n;++i) printf("%d\n",ans[i]);
fclose(stdin);
fclose(stdout);
return 0;
}
例十二:Luogu P5693 EI 的第六分块
线段树维护四个信息,合并时分类讨论。
把维护的四个信息都看作一次函数。
const int N = 4e5+10;
const ll INF = 1e16;
struct Func{//一次函数
ll k,b;
Func(){
k = 0; b = 0;
}
Func(ll tk,ll tb){
k = tk; b = tb;
}
inline Func operator + (const Func&G) const{//函数合并
return Func(k+G.k,b+G.b);
}
inline bool operator < (const Func&G) const{//两个函数有没有交点
return k==G.k && b<G.b || k<G.k;
}
inline ll operator & (const Func&G) const{//求交点
return (G.b-b)/(k-G.k);
}
inline void operator += (const ll&G){//函数向上移动
b += k*G;
}
};
struct Tree{
Func lx,rx,sum,mx; ll dx;//维护区间的四个信息以及阈值
Tree(){
dx = INF;//初始化为无穷大值,表示永远不会对最大值产生影响
}
Tree(Func tlx,Func trx,Func tsum,Func tmx,ll tdx){
lx = tlx; rx = trx; sum = tsum; mx = tmx; dx = tdx;
}
inline void Merge_lx(Func x,Func y,Tree &tmp) const{//求lmax
if(x<y) swap(x,y);
if(x.b>=y.b) tmp.lx = x;
else tmp.lx = y,tmp.dx = Min(tmp.dx,x&y);
}
inline void Merge_rx(Func x,Func y,Tree &tmp) const{//求rmax
if(x<y) swap(x,y);
if(x.b>=y.b) tmp.rx = x;
else tmp.rx = y,tmp.dx = Min(tmp.dx,x&y);
}
inline void Merge_mx(Func x,Func y,Tree &tmp) const{//求mx
if(x<y) swap(x,y);
if(x.b>=y.b) tmp.mx = x;
else tmp.mx = y,tmp.dx = Min(tmp.dx,x&y);
}
inline Tree operator + (const Tree&G) const{//区间合并
Tree tmp; tmp.dx = Min(dx,G.dx); tmp.sum = sum+G.sum;
Merge_lx(lx,sum+G.lx,tmp);Merge_rx(G.rx,G.sum+rx,tmp);
Merge_mx(G.mx,mx,tmp);Merge_mx(tmp.mx,rx+G.lx,tmp);
return tmp;
}
inline void operator += (const ll&G){//区间加
lx += G; rx += G; mx += G; sum += G; dx -= G;
}
}tr[N*3];
//题目说注意卡常,所以直接用zkw
//然后最优解了(雾)
int P=1,DEP=0,st[N*3]; ll tag[N*3];
inline void push_down(int p){//正常push_down
if(tag[p]){
tag[p<<1] += tag[p]; tr[p<<1] += tag[p];
tag[p<<1|1] += tag[p]; tr[p<<1|1] += tag[p];
tag[p] = 0;
}
}
inline void rebuild(int p){
if(tr[p].dx>=0) return ;
int head = 1,tail = 0;
st[++tail] = p; push_down(p);
while(tail>=head){
int ttail = tail;
for(int j=tail,pos;j>=head;--j){
pos = st[j]; //看子节点的子树是否需要更新
if(tr[pos<<1].dx<0) st[++tail]=pos<<1,push_down(pos<<1);
if(tr[pos<<1|1].dx<0) st[++tail]=pos<<1|1,push_down(pos<<1|1);
}
head = ttail+1;
}//重新维护
do{ tr[st[tail]]=tr[st[tail]<<1]+tr[st[tail]<<1|1]; } while(--tail);
}
inline void update(int l,int r,ll k){
l += P-1; r += P+1;//先push_down
for(int dep=DEP;dep;--dep) push_down(l>>dep),push_down(r>>dep);
while(l^1^r){
if(~l&1) tag[l^1]+=k,tr[l^1]+=k,rebuild(l^1);//别忘了rebuild
if(r&1) tag[r^1]+=k,tr[r^1]+=k,rebuild(r^1);
l>>=1;r>>=1;
tr[l] = tr[l<<1]+tr[l<<1|1];
tr[r] = tr[r<<1]+tr[r<<1|1];
}
for(l>>=1; l ;l>>=1) tr[l] = tr[l<<1]+tr[l<<1|1];
}
inline ll query(int l,int r){
l += P-1; r += P+1;//先push_down
for(int dep=DEP;dep;--dep) push_down(l>>dep),push_down(r>>dep);
Tree resl,resr;
while(l^1^r){
//注意左右区间的合并顺序
if(~l&1) resl = resl+tr[l^1];
if(r&1) resr = tr[r^1]+resr;
l>>=1;r>>=1;
}
return (resl+resr).mx.b;
}
int main(){
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
int n,m;read(n,m);
while(P<=n+1) P<<=1,++DEP;
for(int i=1,k;i<=n;++i){
read(k); Func res = Func(1ll,1ll*k);
tr[i+P] = Tree(res,res,res,res,INF);//初始时都是y=x+a[i]
}
for(int i=P-1;i;--i) tr[i] = tr[i<<1]+tr[i<<1|1];
for(int i=1,opt,l,r;i<=m;++i){
read(opt,l,r); ll k;
if(opt==1) read(k),update(l,r,k);
else printf("%lld\n",(k=query(l,r))<0?0ll:k);//区间可以不选
}
// fclose(stdin);
// fclose(stdout);
return 0;
}
例十三 :Luogu P5298 [PKUWC2018] Minimax
线段树合并dp
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
template <typename T>
inline void read(T&x){
int w = 0;x = 0;
char ch = getchar();
while(ch<'0' || ch>'9'){
if(ch=='-') w = 1;
ch = getchar();
}
while(ch>='0' && ch<='9'){
x = (x<<1)+(x<<3)+(ch^48);
ch = getchar();
}
if(w) x = ~x+1;
}
template <typename T,typename...Args>
inline void read(T&t,Args&...args){
read(t);read(args...);
}
template <typename T>
inline T Max(T x,T y){ return (x > y ? x : y); }
template <typename T>
inline T Min(T x,T y){ return (x < y ? x : y); }
template <typename T>
inline T Abs(T x){ return (x < 0 ? ~x+1 : x); }
const int N = 3e5+10,LG = 20;
const ll mod = 998244353,INV = 796898467;
// inline ll quick_pow(ll x,ll y){
// ll res = 1;
// while(y){
// if(y&1) (res *= x)%=mod;
// (x *= x)%=mod;
// y >>= 1;
// }
// return res;
// }
int n,b[N],idx; ll M,ans;
struct{
int ls,rs; ll p;
}g[N];
int P=1,DEP=0,NOW;
int son[N*LG*3][2],rt[N];
struct Tree{
ll val,mul;
Tree(){
val = 0; mul = 1;
}
Tree(ll tval,ll tmul){
val = tval; mul = tmul;
}
inline Tree operator + (const Tree&G) const{
Tree res;
(res.val = val+G.val)%=mod;
return res;
}
inline void operator += (const ll&G){
(val += G)%=mod;
}
inline void operator *= (const ll&G){
if(G==1) return ;
(val *= G)%=mod;
(mul *= G)%=mod;
}
}tr[N*3*LG];
inline void push_down(int p){
tr[son[p][0]] *= tr[p].mul;
tr[son[p][1]] *= tr[p].mul;
tr[p].mul = 1;
}
inline void update(int i,int l,ll k){
int tl = l+P;
rt[i] ? 0 : rt[i] = ++NOW;
l = rt[i]; tr[l] += k;
for(int dep=DEP-1;dep>=0;--dep,tr[l]+=k){
if((tl>>dep)&1) son[l][1]?0:son[l][1]=++NOW,l=son[l][1];
else son[l][0]?0:son[l][0]=++NOW,l=son[l][0];
}
}
inline ll get(ll s,int p,int t){
return ((t ? M : (1-M+mod))*tr[son[p][t^1]].val%mod+s)%mod;
}
struct ST{
int p1,p2;
ll mul1_0,mul1_1;
ll mul2_0,mul2_1;
ST(){
p1 = p2 = 0;
mul1_0 = mul1_1 = 0;
mul2_0 = mul2_1 = 0;
}
ST(int tp1,int tp2,ll mul1,ll mul2){
p1 = tp1; p2 = tp2;
push_down(p1); push_down(p2);
mul1_0 = get(mul1,p2,0);
mul1_1 = get(mul1,p2,1);
mul2_0 = get(mul2,p1,0);
mul2_1 = get(mul2,p1,1);
}
ST(int tp1,int tp2){
p1 = tp1; p2 = tp2;
push_down(p1);
}
}st[N*3];
inline void merge(int p){
int u = rt[g[p].ls],v = rt[g[p].rs];
rt[p] = u;
int head = 1,tail = 0;
// push_down(u); push_down(v);
st[++tail] = ST(u,v,0,0);
for(int dep=DEP-1;dep>=0;--dep){
int ttail = tail;
if(tail<head) break;
for(int j=tail;j>=head;--j){
u = st[j].p1; v = st[j].p2;
// push_down(u); push_down(v);
if(son[u][0]&&son[v][0]) st[++tail]=ST(son[u][0],son[v][0],st[j].mul1_0,st[j].mul2_0);
else if(son[u][0]&&!son[v][0]) tr[son[u][0]] *= st[j].mul1_0;
else if(!son[u][0]&&son[v][0]){
tr[son[v][0]] *= st[j].mul2_0;
son[u][0] = son[v][0];
}
// cout << u << " " << v << endl;
if(son[u][1]&&son[v][1]) st[++tail]=ST(son[u][1],son[v][1],st[j].mul1_1,st[j].mul2_1);
else if(son[u][1]&&!son[v][1]) tr[son[u][1]] *= st[j].mul1_1;
else if(!son[u][1]&&son[v][1]){
tr[son[v][1]] *= st[j].mul2_1;
son[u][1] = son[v][1];
}
}
head = ttail+1;
}
// tail = Min(tail,head);
do{ tr[st[tail].p1]=tr[son[st[tail].p1][0]]+tr[son[st[tail].p1][1]];}while(--tail);
}
inline void dfs(int now){
if(!g[now].ls) update(now,g[now].p,1);
else if(!g[now].rs){
dfs(g[now].ls);
rt[now] = rt[g[now].ls];
}
else{
dfs(g[now].ls); dfs(g[now].rs);
M = g[now].p;
merge(now);
}
}
inline void get_ans(){
int head = 1,tail = 0;
// rt -> st
st[++tail] = ST(rt[1],1);
for(int dep=DEP-1;dep>=0;--dep){
int ttail = tail;
if(tail<head) break;
for(int j=tail;j>=head;--j){
// cout << st[j].p1 << " " << st[j].p2 << endl;
if(son[st[j].p1][0]) st[++tail]=ST(son[st[j].p1][0],st[j].p2<<1);
if(son[st[j].p1][1]) st[++tail]=ST(son[st[j].p1][1],st[j].p2<<1|1);
}
head = ttail+1;
}
while(tail>=head){
int p1 = st[tail].p1,p2 = st[tail].p2;
(ans += tr[p1].val*tr[p1].val%mod*(p2-P)%mod*b[p2-P])%=mod;
--tail;
}
}
int main(){
// freopen("data.out","r",stdin);
// freopen("put.out","w",stdout);
read(n);
for(int i=1,u;i<=n;++i){
read(u);
if(!g[u].ls) g[u].ls = i;
else g[u].rs = i;
}
// INV = quick_pow(10000,mod-2);
for(int i=1;i<=n;++i){
read(g[i].p);
if(!g[i].ls) b[++idx] = g[i].p;
else (g[i].p *= INV)%=mod;
}
sort(b+1,b+1+idx);
for(int i=1;i<=n;++i){
if(!g[i].ls) g[i].p = lower_bound(b+1,b+1+idx,g[i].p)-b;
}
while(P<=idx+1) P<<=1,++DEP;
dfs(1);
get_ans();
printf("%lld",ans);
fclose(stdin);
fclose(stdout);
return 0;
}

浙公网安备 33010602011771号