线段树练习

发布时间 2024-01-13 18:53:46作者: Liuyc07
# Ⅰ.差分与前缀和 ## P2184 贪婪大陆 **题意** :给定防线长度 $n$ 和操作次数 $m$, 每次在 [$l$ ,$r$] 内布下一种雷,查询区间雷的种类数。 **分析** : 用线段的方式表示区间布的雷 : ![](https://cdn.luogu.com.cn/upload/image_hosting/s6vfsg2g.png) 如[ 2 , 4 ]内的种类 = [ 1 , 4 ]内的起点 - [ 1 , 2 ]内的终点 ## P1438 无聊的数列 **题意** : 区间加等差数列,区间查询。 **分析** : 将原序列差分,即可转化为区间加。 ![](https://cdn.luogu.com.cn/upload/image_hosting/log4k6iw.png) **Summary**:在[ $l$ ,$r$ ] 内加上一个首项为 $k$ ,公差为 $D$ 的等差数列,等价于在差分数组上的 $l$ 加 $k$ , 然后在在 [ $l+1$ , $r$] 加上$D$, 最后在 $r+1$ 减去 $[ k+D*(r-l) ]$。 # Ⅱ. 贪心 ## P1607 [ USACO09FEB ] Fair Shuttle G **题意** : 从$1$ 到 $n$ 在有 $k$ 组cow,想从 $s$ 到 $e$, bus只能单向行驶,且容量为c, 问最大能有多少cow能达成目的。 (其中每组cow可以只有部分上车) **分析** : 用线段树模拟上车的过程,显然应该按照右端点排序。 ![](https://cdn.luogu.com.cn/upload/image_hosting/x4vh0qu4.png) 然后就可以边模拟边用线段树维护已经在车上的最大人数。 ## P1937 [ USACO10MAR ] Barn Allocation G **题意** : $n$ 个点,每个点有一个容量 $c_i$, $m$ 次操作,在$[l , r]$ 内加上 $1$,问最多能加几次。 **分析** : 做完上一道就很点了,维护区间minn,可以就 $-1$,$ans$++ # Ⅳ. 最大子段和 单独拿出来了,~~也算是一类吧~~。目前做过的都比较明显。 ## P4513 小白逛公园 & GSS3 - Can you answer these queries III **题意** : 1. 查询区间最大子段和; 2.单点修改 **分析** : 板子。 将子树 $ls$ 和 $rs$ 的节点信息上传到父节点 $u$ 时,对于 $u$ 维护的序列中,和最大的子段有两种情况: $case1:$ 子段不经过中点,那么 $u$ 的答案为 $ls$ 和 $rs$ 的答案的最大值。 $case2:$ 子段经过了中点。这种情况比较复杂,因为我们无法知道子树的答案所对应的序列。这也是本题的难点所在。 接下来对第 22 种情况进行重点分析: 我们记 $ms$ 为区间最长子段和,$sum$ 为区间和,$prel$ 和 $prer$ 分别表示从区间左端点和右端点开始的最大子段和。 考虑 $ pushup $:$sum$ 可以直接上传,$prel[u]=\max(prel[ls],sum[ls]+prel[rs]) \quad $($prer$ 同理) $ms[u]=\max(res[ls],ms[rs],prer[ls]+prel[rs]) \quad $ #### Notice: **$query$有~~一点~~变化** : ```cpp Info query(int u,int l,int r){ if(tr[u].l>=l and tr[u].r<=r){ return tr[u]; } if(r<=mid) return query(ls,l,r); //千万注意 if(l>mid) return query(rs,l,r); Info T, L=query(ls,l,r), R=query(rs,l,r); T.sum=L.sum+R.sum; T.lsum=max(L.lsum,L.sum+R.lsum); T.rsum=max(R.rsum,R.sum+L.rsum); T.ms=max(max(L.ms,R.ms),L.rsum+R.lsum); return T; } ``` 参考:[Siyuan](https://www.luogu.com.cn/user/49725),[SP1716](https://www.luogu.com.cn/problem/solution/SP1716) sol ## P2572 [SCOI2010] 序列操作 **题意** : - `0 l r` 把 $[l, r]$ 区间内的所有数全变成 $0$; - `1 l r` 把 $[l, r]$ 区间内的所有数全变成 $1$; - `2 l r` 把 $[l,r]$ 区间内的所有数全部取反,也就是说把所有的 $0$ 变成 $1$,把所有的 $1$ 变成 $0$; - `3 l r` 询问 $[l, r]$ 区间内总共有多少个 $1$; - `4 l r` 询问 $[l, r]$ 区间内最多有多少个连续的 $1$。 **分析** : 类似最大子段和的思想,相当于记录了2个最大子段和(0和1),同时维护前后缀$sum$。 $Notice : $ 前后缀 $sum$ 的更新 $\quad\quad\quad u.prel_0= ls.sum_o ~?~ ls.prel_0 : ls.sum_0 + rs.prel_o$ ## P4118 [Ynoi2018] 末日时在做什么?有没有空?可以来拯救吗? **~~~~不会~~~~** # Ⅴ. 优化建图 **Statement** :大多与最短路有关(~~其它的太难了也不去做~~) 建一棵in树和out树,in树由上到下连边,再连向out树,out树自下而上连边。这样,如果要连 $ 1$ 到 $[ 4 ,6 ]$ 的边,只需于fa节点相连。 $O(n) -$> $O( \log_ 2x ) $ ![](https://img2020.cnblogs.com/blog/1859218/202010/1859218-20201003175442575-1059055629.png#pic_center) 参考:[maoyiting](https://www.cnblogs.com/maoyiting/p/13764109.html#/cnblog/works/article/13764109) **Notice** : 实际上不需要开structure,只需要记录左右 $l,r$,以及一个极大的 $\Delta K$ ,为了分别表示 $in$ 树和 $out$ 树,然后直接建图即可。 ## CF786B Legacy **题意** : 1. 把$u,v$ 连 $w$ 的边  2.把 $u$ 连向 $[l ,r]$ 3.把 $[l , r]$连向u. 求s号点到其它的最短路。 **sol** : 线段树优化建图,$dijkstra$. **code** : ```cpp void add(){ } void build(int u,int l,int r){ if(l==r){ a[l]=u; return; } add(u,ls,0); add(u,rs,0); add(ls+k,u+k,0); add(rs+k,u+k,0); build(ls,l,mid); build(rs,mid + 1,r); } void modify(int u,int l,int r,int x,int y,int v,int w){ if(l>=x and r<=y){ if(opt==2) add(v+k,u,w); else add(u+k,v,w); return; } if(x<=mid) modify(ls,l,mid,x,y,v,w); if(y>mid) modify(rs,mid+1,r,x,y,v,w); } void djkstra(){ } signed main(){ read(n,m,s); build(1,1,n); rep(i,1,n) add(a[i],a[i]+k,0),add(a[i]+k,a[i],0); rep(i,1,m){ read(opt); if(opt==1){ int u,v,w; read(u,v,w); add(a[u]+k,a[v],w); } else{ int u,l,r,w; read(u,l,r,w); modify(1,1,n,l,r,a[u],w); } } dijkstra(a[s]+k); rep(i,1,n){ printf("%lld ",dis[a[i]]==0x3f3f3f3f3f3f3f3fll? -1:dis[a[i]]); } return 0; } ``` # Ⅵ. 二分 权值线段树上可以直接二分,应用:逆序对 ## P2824 [HEOI2016/TJOI2016] 排序 **题意** : 一个排列n,m次操作,把$[l,r]$升序或降序,求最后完成操作后第p位置上的数。 **分析** : 1. 暴力排序 + 超快读,喜提80分 2. 正解 : 线段树 + 二分。 二分第p位置上的数,把小于p的数改为0,大于等于p的数改为1,。记$[l,r]$内1的个数为 $ cnt $ ,如果把$[l,r]$升序,就把 $r-cnt+1$ 的数都改为$1$,其余改为$0$;降序相反。第一个是$1$的位置就是答案。 **code** : ```cpp void pushup(int u){ tr[u].sum=tr[ls].sum+tr[rs].sum; } void pushdown(int u){ if(~tr[u].tag){ tr[ls].tag=tr[rs].tag=tr[u].tag; tr[ls].sum=tr[u].tag*len(tr[ls]); tr[rs].sum=tr[u].tag*len(tr[rs]); tr[u].tag=-1; } } void build(int u,int l,int r,int k){ tr[u]={l,r,0,-1}; if(l==r){ tr[u].sum=(a[l]>=k); return; } build(ls,l,mid,k); build(rs,mid+1,r,k); pushup(u); } void assign(int u,int l,int r,int k){ if(tr[u].l>=l and tr[u].r<=r){ tr[u].sum=len(tr[u])*k; tr[u].tag=k; return; } pushdown(u); if(l<=mid) assign(ls,l,r,k); if(r>mid) assign(rs,l,r,k); pushup(u); } int query(int u,int l,int r){ return sum; } int check(int k){ build(1,1,n,k); rep(i,1,m){ int l=x[i],r=y[i],k=op[i]; int cnt=query(1,l,r); if(!k){ assign(1,r-cnt+1,r,1); assign(1,l,r-cnt,0); } else{ assign(1,l,l+cnt-1,1); assign(1,l+cnt,r,0); } } return query(1,p,p); } int main(){ ...... int l=0,r=n; while(l>1; if(check(MID)) l=MID; else r=MID-1; } cout<len_0$,那么就从$l_1$开始,如果$len_1>1; if(find(1,l1,M,0)