線分ツリー C/C++ 実装
本質は左右の端点を追加したツリー構造です
セグメントツリー
データ型構造
struct node{
int l,r; //l左端点 r右端点
int val; //val值域
}n[maxn]; //n[i]表示标号为i的树的结点
基本操作
セグメントツリーの構築
void make(int left,int right, int num);
パラメータの説明:
left—right: 左端点—右端点、最大間隔。
num: ラベル、通常は 1。
ノードを追加
void add(int i,int j,int num);
パラメータの説明:
i: 追加される目的点。
j: 加算するポイント数。
num: ラベル、通常は 1。
ノードの削除
void sub(int i,int j,int num);
パラメータの説明:
i: 追加される目的点。
j: 加算するポイント数。
num: ラベル、通常は 1。
クエリノード
void query(int l, int r,int num);
パラメータの説明:
lr: クエリ間隔。この関数は、sum に格納されている重みの合計を使用して lr の数をクエリします。
num: ラベル、通常は 1。
実現原理
セグメントツリーの構築
使用の性質:
1> i というラベルが付けられたノードの場合、その左の子は 2i というラベルが付けられ、その右の子は 2i+1 というラベルが付けられ、その父親は i/2 というラベルが付けられます。
2> 線分ツリーにおいて、両端が a と b であり、その左の子の両端が a, Mid であり、右の子の両端が Mid+1, b である点。
3> 左右のエンドポイントが等しい場合、リーフノードにアクセスします
アイデア:
各ノードごとに
val は次のように計算されます: val = left.val + right.val
ノードを追加
トップダウンツリーの操作、ルートからノードまで追加していく操作です。
アイデア:
ルートから下にアクセスします。i が左側の子の間隔にある場合は + j、それ以外の場合は右側の子にアクセスします。( i は左側の子には存在しません。右側の子に存在する必要があります)
ルートから開始し、リーフ ノードで停止します
ノードの削除
ノードの逆演算を追加します。「+」を「-」に変更するだけです。
クエリノード
現在の点の両端を a、b、目標点の両端を l、r とします。
分類法を使用する
l と r がどのセクションにあるか、どのセクションにアクセスするか
アイデア:
C++ コードの実装
セグメントツリーを構築する
void make(int x, int y, int num){
//构造
t[num].l=x,t[num].r=y;
if(x==y)
t[num].val=val; //到叶子结点赋值,结束
else{
make(x, ( x + y ) / 2, 2 * num);//递归构造左子树
make(( x + y ) / 2 + 1, y, 2 * num + 1);//递归构造右子树
t[num].val = t[2*num].val + t[2*num+1].val;//计算val
}
}
ノードを追加
void add(int i, int j, int num){
//增加
t[num].val += j;//val+j
if(t[num].l==i && t[num].r==i)//找到结点
return;
if(i>(t[num].l + t[num].r) / 2)//i在右孩子中
add(i, j, 2 * num + 1);
else //i在左孩子中
add(i, j, 2 * num);
}
ノードの削除
void add(int i, int j, int num){
//增加
t[num].val -= j;//val+j
if(t[num].l==i && t[num].r==i)//找到结点
return;
if(i>(t[num].l + t[num].r) / 2)//i在右孩子中
add(i, j, 2 * num + 1);
else //i在左孩子中
add(i, j, 2 * num);
}
クエリノード
void query(int l, int r, int num){
//查询
if(l<=t[num].l && r>=t[num].r)//在目标区间内,记录权值和SUM
SUM += t[num].val;
else{
//不在目标区间内,进行分类
int mid = (t[num].l + t[num].r)/2;
if(l>mid)//在右孩子中
query(l, r, 2 * num + 1);
else if(r<=mid)//在左孩子中
query(l, r, 2 * num);
else{
//左右孩子中均有
query(l, r, 2 * num);
query(l, r, 2 * num + 1);
}
}
}
遅延タグの最適化
間隔更新: 連続間隔の値を変更します。
遅延タグの最適化:
名前が示すように、怠惰なマークの最適化 (ご存知のとおり、怠け者が効率を生み出す)
重要な考え方: 変更するときは、クエリに役立つポイントのみを変更し、完全に更新されていないポイントには_lazily_ マークを付け、必要に応じて更新します。
応用例
- P3372 [テンプレート] 線分ツリー 1
タイトルの説明:
タイトルにあるように、一連の数値が与えられた場合、次の 2 つの操作を実行する必要があります。
1. 特定の間隔で各数値に k を加算します。
2. 特定の区間内の各数値の合計を求めます。
コードは以下のように表示されます:
#include<iostream>
#include<cstdio>
#define MAXN 1000001
#define ll long long
using namespace std;
unsigned ll n,m,a[MAXN],ans[MAXN<<2],tag[MAXN<<2];
inline ll ls(ll x)
{
return x<<1;
}
inline ll rs(ll x)
{
return x<<1|1;
}
void scan()
{
cin>>n>>m;
for(ll i=1;i<=n;i++)
scanf("%lld",&a[i]);
}
inline void push_up(ll p)
{
ans[p]=ans[ls(p)]+ans[rs(p)];
}
void build(ll p,ll l,ll r)
{
tag[p]=0;
if(l==r){
ans[p]=a[l];return ;}
ll mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
push_up(p);
}
inline void f(ll p,ll l,ll r,ll k)
{
tag[p]=tag[p]+k;
ans[p]=ans[p]+k*(r-l+1);
}
inline void push_down(ll p,ll l,ll r)
{
ll mid=(l+r)>>1;
f(ls(p),l,mid,tag[p]);
f(rs(p),mid+1,r,tag[p]);
tag[p]=0;
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
if(nl<=l&&r<=nr)
{
ans[p]+=k*(r-l+1);
tag[p]+=k;
return ;
}
push_down(p,l,r);
ll mid=(l+r)>>1;
if(nl<=mid)update(nl,nr,l,mid,ls(p),k);
if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
push_up(p);
}
ll query(ll q_x,ll q_y,ll l,ll r,ll p)
{
ll res=0;
if(q_x<=l&&r<=q_y)return ans[p];
ll mid=(l+r)>>1;
push_down(p,l,r);
if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p));
if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p));
return res;
}
int main()
{
ll a1,b,c,d,e,f;
scan();
build(1,1,n);
while(m--)
{
scanf("%lld",&a1);
switch(a1)
{
case 1:{
scanf("%lld%lld%lld",&b,&c,&d);
update(b,c,1,n,1,d);
break;
}
case 2:{
scanf("%lld%lld",&e,&f);
printf("%lld\n",query(e,f,1,n,1));
break;
}
}
}
return 0;
}
- P3373 [テンプレート] 線分ツリー 2
トピックの説明:
タイトルにあるように、一連の数値が与えられた場合、次の 3 つの操作を実行する必要があります。 1.
特定の間隔内の各数値に x を掛けます
。 2. 内の各数値に x を加算します。特定の間隔
3. 検索 特定の間隔内の各数値の合計のコードは
次のとおりです。
#include <iostream>
#include <cstdio>
using namespace std;
//题目中给的p
int p;
//暂存数列的数组
long long a[100007];
//线段树结构体,v表示此时的答案,mul表示乘法意义上的lazytag,add是加法意义上的
struct node{
long long v, mul, add;
}st[400007];
//buildtree
void bt(int root, int l, int r){
//初始化lazytag
st[root].mul=1;
st[root].add=0;
if(l==r){
st[root].v=a[l];
}
else{
int m=(l+r)/2;
bt(root*2, l, m);
bt(root*2+1, m+1, r);
st[root].v=st[root*2].v+st[root*2+1].v;
}
st[root].v%=p;
return ;
}
//核心代码,维护lazytag
void pushdown(int root, int l, int r){
int m=(l+r)/2;
//根据我们规定的优先度,儿子的值=此刻儿子的值*爸爸的乘法lazytag+儿子的区间长度*爸爸的加法lazytag
st[root*2].v=(st[root*2].v*st[root].mul+st[root].add*(m-l+1))%p;
st[root*2+1].v=(st[root*2+1].v*st[root].mul+st[root].add*(r-m))%p;
//很好维护的lazytag
st[root*2].mul=(st[root*2].mul*st[root].mul)%p;
st[root*2+1].mul=(st[root*2+1].mul*st[root].mul)%p;
st[root*2].add=(st[root*2].add*st[root].mul+st[root].add)%p;
st[root*2+1].add=(st[root*2+1].add*st[root].mul+st[root].add)%p;
//把父节点的值初始化
st[root].mul=1;
st[root].add=0;
return ;
}
//update1,乘法,stdl此刻区间的左边,stdr此刻区间的右边,l给出的左边,r给出的右边
void ud1(int root, int stdl, int stdr, int l, int r, long long k){
//假如本区间和给出的区间没有交集
if(r<stdl || stdr<l){
return ;
}
//假如给出的区间包含本区间
if(l<=stdl && stdr<=r){
st[root].v=(st[root].v*k)%p;
st[root].mul=(st[root].mul*k)%p;
st[root].add=(st[root].add*k)%p;
return ;
}
//假如给出的区间和本区间有交集,但是也有不交叉的部分
//先传递lazytag
pushdown(root, stdl, stdr);
int m=(stdl+stdr)/2;
ud1(root*2, stdl, m, l, r, k);
ud1(root*2+1, m+1, stdr, l, r, k);
st[root].v=(st[root*2].v+st[root*2+1].v)%p;
return ;
}
//update2,加法,和乘法同理
void ud2(int root, int stdl, int stdr, int l, int r, long long k){
if(r<stdl || stdr<l){
return ;
}
if(l<=stdl && stdr<=r){
st[root].add=(st[root].add+k)%p;
st[root].v=(st[root].v+k*(stdr-stdl+1))%p;
return ;
}
pushdown(root, stdl, stdr);
int m=(stdl+stdr)/2;
ud2(root*2, stdl, m, l, r, k);
ud2(root*2+1, m+1, stdr, l, r, k);
st[root].v=(st[root*2].v+st[root*2+1].v)%p;
return ;
}
//访问,和update一样
long long query(int root, int stdl, int stdr, int l, int r){
if(r<stdl || stdr<l){
return 0;
}
if(l<=stdl && stdr<=r){
return st[root].v;
}
pushdown(root, stdl, stdr);
int m=(stdl+stdr)/2;
return (query(root*2, stdl, m, l, r)+query(root*2+1, m+1, stdr, l, r))%p;
}
int main(){
int n, m;
scanf("%d%d%d", &n, &m, &p);
for(int i=1; i<=n; i++){
scanf("%lld", &a[i]);
}
bt(1, 1, n);
while(m--){
int chk;
scanf("%d", &chk);
int x, y;
long long k;
if(chk==1){
scanf("%d%d%lld", &x, &y, &k);
ud1(1, 1, n, x, y, k);
}
else if(chk==2){
scanf("%d%d%lld", &x, &y, &k);
ud2(1, 1, n, x, y, k);
}
else{
scanf("%d%d", &x, &y);
printf("%lld\n", query(1, 1, n, x, y));
}
}
return 0;
}