ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P3372 洛谷 线段树1模板的多种解法

2022-01-31 11:32:25  阅读:198  来源: 互联网

标签:洛谷 int Rot tree data add Ask P3372 模板


线段树1的多种解法

B y By By q w q qwq qwq_ v o l c a n o ( D a r k volcano(Dark volcano(Dark _ v o l c a n o ) volcano) volcano)
l u o g u luogu luogu:qwq_volcano
Link:P3372

此篇文章着重于介绍树状数组解法

Tips:记得开 l o n g l o n g long long longlong


Update:2022/1/31 年三十

线段树

普通线段树:
不用多说,直接上code

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
struct node{
	int tag;
	int data;
	int l;
	int r;
}tree[400001];
int n,m,a[100001],ans;
int yezi(int k){//判断叶子节点
	return (tree[k].l==tree[k].r);
}
void build(int l,int r,int k){//建树
	tree[k].l=l;tree[k].r=r;
	if(yezi(k)){
		tree[k].data=a[tree[k].l];
		return ;
	}
	int mid=(tree[k].l+tree[k].r)>>1;
	build(tree[k].l,mid,k*2);
	build(mid+1,tree[k].r,k*2+1);
	tree[k].data=tree[k*2].data+tree[k*2+1].data;
}
void push_down(int k){//懒标记下传
	tree[k*2+1].tag+=tree[k].tag;
	tree[k*2].tag+=tree[k].tag;
	tree[k*2+1].data+=tree[k].tag*(tree[k*2+1].r-tree[k*2+1].l+1);
	tree[k*2].data+=tree[k].tag*(tree[k*2].r-tree[k*2].l+1);
	tree[k].tag=0;
}
void Sum(int x,int y,int k){//区间求和 [x~y]
	if(x<=tree[k].l&&y>=tree[k].r){
		ans+=tree[k].data;
		return ;
	}
	if(tree[k].tag) push_down(k);
	int mid=(tree[k].l+tree[k].r)>>1;
	if(x<=mid) Sum(x,y,k*2);
	if(y>mid) Sum(x,y,k*2+1);
}
int Ask(int k,int q){//单点查询 本文并没有用到 可以用区间查询代替
	if(yezi(k)) return tree[k].data;
	int mid=(tree[k].l+tree[k].r)>>1;
    if(tree[k].tag) push_down(k);
	if(q<=mid) Ask(2*k,q);
	else Ask(2*k+1,q); 
}
void Add(int k,int q){//单点加 同样没有用
	if(tree[k].l==tree[k].r){
		tree[k].data+=q;
		tree[k].tag+=q;
		return ;
	}
	if(tree[k].tag) push_down(k);
	int mid=(tree[k].l+tree[k].r)>>1;
	if(q<=mid) Add(k*2,q);
	else Add(k*2+1,q);
	tree[k].data=tree[k*2].data+tree[k*2+1].data;
}
void Change(int k,int x,int y,int q){//区间加
	if(x<=tree[k].l&&y>=tree[k].r){
		tree[k].data+=(tree[k].r-tree[k].l+1)*q;
		tree[k].tag+=q;
		return ;
	}
	if(tree[k].tag) push_down(k);
	int mid=(tree[k].l+tree[k].r)>>1;
	if(x<=mid) Change(k*2,x,y,q);
	if(y>mid) Change(k*2+1,x,y,q);
	tree[k].data=tree[k*2].data+tree[k*2+1].data;
}
signed main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	    scanf("%d",a+i);
	build(1,n,1);
	for(int i=1;i<=m;i++){
		int w,x,y,k;
		scanf("%lld%lld%lld",&w,&x,&y);
		if(w==1){
			scanf("%lld",&k);
			Change(1,x,y,k);
		}
		else{
			ans=0;
			Sum(x,y,1);
			printf("%lld\n",ans);
		}
	}
}

指针线段树 (慢300-400ms,吸氧后差不多)

//ask_interval_sum 区间求和 add_interval区间加
#include<iostream>
using namespace std;
template <typename ITEM>
class seg_tree{
	private:
	struct node{
		ITEM data;
		ITEM Lazytag_add;
		int l,r;
		node *lc,*rc;
	}*root;
	void push_down(node **Rot){
		(*Rot)->lc->Lazytag_add+=(*Rot)->Lazytag_add;
		(*Rot)->rc->Lazytag_add+=(*Rot)->Lazytag_add;
		(*Rot)->lc->data+=((*Rot)->lc->r-(*Rot)->lc->l+1)*((*Rot)->Lazytag_add);
		(*Rot)->rc->data+=((*Rot)->rc->r-(*Rot)->rc->l+1)*((*Rot)->Lazytag_add);
		(*Rot)->Lazytag_add=0;
	}
	node *hbuild(ITEM a[],node ** Rot,int l,int r){
		*Rot=new node;
		(*Rot)->l=l;
		(*Rot)->r=r;
		if(l==r){
			(*Rot)->data=a[l];
			(*Rot)->lc=NULL;
			(*Rot)->rc=NULL;
			return (*Rot);
		}
		int mid=(l+r)>>1;
		(*Rot)->lc=hbuild(a,&((*Rot)->lc),l,mid);
		(*Rot)->rc=hbuild(a,&((*Rot)->rc),mid+1,r);
		(*Rot)->data=(*Rot)->lc->data+(*Rot)->rc->data;
		return (*Rot);
	}

	ITEM Ask_I_S(node **Rot,int ql,int qr){
		ITEM res=0;
		if((*Rot)->l>=ql&&(*Rot)->r<=qr){
			res+=(*Rot)->data;
			return res;
		}
		if((*Rot)->Lazytag_add) push_down(&(*Rot));
		int mid=((*Rot)->l+(*Rot)->r)>>1;
		if(ql<=mid) res+=Ask_I_S(&((*Rot)->lc),ql,qr);
		if(qr>mid) res+=Ask_I_S(&(*Rot)->rc,ql,qr);
		return res;
	}
	void ADD_I_S(node **Rot,int ql,int qr,int k){
		if((*Rot)->l>=ql&&(*Rot)->r<=qr){
			(*Rot)->data+=((*Rot)->r-(*Rot)->l+1)*k;
			(*Rot)->Lazytag_add+=k;
			return ;
		}
		if((*Rot)->Lazytag_add){push_down(&(*Rot));}
		int mid=((*Rot)->l+(*Rot)->r)>>1;
		if(ql<=mid) ADD_I_S(&((*Rot)->lc),ql,qr,k);
		if(qr>mid) ADD_I_S(&(*Rot)->rc,ql,qr,k);
		(*Rot)->data=(*Rot)->lc->data+(*Rot)->rc->data;
	}
	public:
	void build(ITEM a[],int l,int r){
		hbuild(a,&root,l,r);
	}
	void Show(node *ROt){
		cout<<ROt->data<<" ";
		if(ROt->lc) Show(ROt->lc);
		if(ROt->rc) Show(ROt->rc);
	}
	void DebugShow(){
		Show(root);
	}
	ITEM ask_interval_sum(int l,int r){
		return Ask_I_S(&root,l,r);
	}
	void add_interval(int l,int r,int k){
        ADD_I_S(&root,l,r,k);
	}
};
seg_tree<long long> T;
long long a[100001];
int n,q;
int main(){
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>a[i];
	T.build(a,1,n);
	while(q--){
        int tag;
        cin>>tag;
        if(tag==1){
            int a,b,c;
            cin>>a>>b>>c;
            T.add_interval(a,b,c);
        }
        if(tag==2){
            int a,b;
            cin>>a>>b;
            cout<<T.ask_interval_sum(a,b)<<endl;
        }
	}
}

分块

浅显易懂,但是有亿点慢(能过)
约2s,吸氧快1s
自己研究出的很独特的写法

//极简分块 ,写法有一点不同
#include<iostream>
#include<cstdio>
#include<cmath>
#define int long long
using namespace std;
int n;
int T,L;//块数,块长
int a[100001];
int sum[100001],addit[100001];//sum[p]表示第p个块的和,addit[p]表示第p个块的加法标记
int BEGIN(int p){return L*(p-1)+1;}//求第p个块的前端
int END(int p){return min(L*p,n);}//求第p个块的后端
int NUM(int p){return ceil(double(p)/L);} //求包含数a[i]的块的编号
int LENGTH(int p){//求第i个块长
	if(END(p)==n) return n-(T-1)*L;
	else return L;
}
void Prework(){//预处理,大概是最简的吧
	T=ceil(sqrt(double(n))),L=ceil(double(n)/T);
	for(int i=1;i<=n;i++) sum[NUM(i)]+=a[i];
}
//分块的核心,大段维护,小段暴力
void add(int l,int r,int c){
	for(int i=l;i<=END(NUM(l))&&i<=r;++i) a[i]+=c,sum[NUM(i)]+=c;//不是整块的直接暴力加
	if(NUM(l)!=NUM(r)) for(int i=r;i>=BEGIN(NUM(r))&&i>=l;--i) a[i]+=c,sum[NUM(i)]+=c;//同上
	for(int i=NUM(l)+1;i<NUM(r);i++) sum[i]+=(c*LENGTH(i)),addit[i]+=c;//大块维护
}
//查询整体思路和加一样
int ask(int l,int r){
	int ans=0;
	for(int i=l;i<=END(NUM(l))&&i<=r;++i) ans+=(a[i]+addit[NUM(i)]);
	if(NUM(l)!=NUM(r)) for(int i=r;i>=BEGIN(NUM(r))&&i>=l;--i) ans+=(a[i]+addit[NUM(i)]);
	for(int i=NUM(l)+1;i<NUM(r);i++) ans+=sum[i];
	return ans;
}
int Q;
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>Q;
	for(int i=1;i<=n;i++) cin>>a[i];
	Prework();
	while(Q--){
		int opt,x,y;
		cin>>opt>>x>>y;
		if(opt==1){int k;cin>>k;add(x,y,k);}
		else {cout<<ask(x,y)<<'\n';}
	}
}

树状数组

给出树状数组函数

int lowbit(int x){//求2^(末尾0的个数) 这里用^代表幂,不是异或
	return x&(-x);
}
void add(int x,int k){//单点加
	for(;x<=n;x+=lowbit(x)) c[x]+=k;
}
int ask(int x){//求前缀和
	int ans=0;
	for(;x>0;x-=lowbit(x)) ans+=c[x];
	return ans;
}

前置知识: 差分,树状数组 “单点更新,区间查询”、“区间更新,单点查询”

“区间更新,区间查询”:
由"区间更新,单点查询"的前置知识,可以得出此题区间更新仍需要维护差分数组
设数列长度为 n n n,原数组为 a [ 1 a[1 a[1~ n ] n] n]
操作区间为 l l l~ r r r
则差分数组 d [ i ] = a [ i ] − a [ i − 1 ] ; d[i]=a[i]-a[i-1]; d[i]=a[i]−a[i−1];

区间更新?

相当于用树状数组维护差分数组
用树状数组维护差分数组,初始化时遍历1~n 不停 a d d ( i , a [ i ] − a [ i − 1 ] ) ; add(i,a[i]-a[i-1]); add(i,a[i]−a[i−1]);
更新只需要修改两次,假设加上w ,即 a d d ( l , w ) add(l,w) add(l,w) a d d ( r + 1 , − w ) add(r+1,-w) add(r+1,−w)

单点查询?

就是求差分数组的前缀和,如果查询第i个数就是查询代表差分数组的树状数组,即 ask(i)

区间查询?

设查询原数组的前缀和为 A s k ( x ) Ask(x) Ask(x)
那么 A s k ( x ) = ∑ i = x r a s k ( i ) Ask(x)=\sum_{i=x}^r ask(i) Ask(x)=∑i=xr​ask(i)
让我们展开它
           A s k ( x ) = a s k ( 1 ) + a s k ( 2 ) + . . . + a s k ( x ) Ask(x)=ask(1)+ask(2)+...+ask(x) Ask(x)=ask(1)+ask(2)+...+ask(x)
因为 a s k ask ask 又是求前缀和 即 a s k ( i ) = ∑ j = 1 i d [ j ] ask(i)=\sum_{j=1}^i d[j] ask(i)=∑j=1i​d[j]
所以
         A s k ( x ) = ∑ j = 1 1 d [ j ] + ∑ j = 1 2 d [ j ] + . . . + ∑ j = 1 x d [ j ] Ask(x)=\sum_{j=1}^1 d[j]+\sum_{j=1}^2 d[j]+...+\sum_{j=1}^x d[j] Ask(x)=∑j=11​d[j]+∑j=12​d[j]+...+∑j=1x​d[j]

         A s k ( x ) = x × d [ 1 ] + ( x − 1 ) × d [ 2 ] + . . . + 1 × d [ x ] Ask(x)=x\times d[1]+(x-1)\times d[2]+...+1\times d[x] Ask(x)=x×d[1]+(x−1)×d[2]+...+1×d[x]
又可以变成
             A s k ( x ) = ∑ i = 1 x d [ i ] × ( x − i + 1 ) Ask(x)=\sum_{i=1}^x d[i]\times (x-i+1) Ask(x)=∑i=1x​d[i]×(x−i+1)
利用乘法分配律,又可以变成
          A s k ( x ) = ( x + 1 ) × ∑ i = 1 x d [ i ] − ∑ i = 1 x d [ i ] × i Ask(x)=(x+1)\times \sum_{i=1}^x d[i] - \sum_{i=1}^xd[i]\times i Ask(x)=(x+1)×∑i=1x​d[i]−∑i=1x​d[i]×i

           A s k ( x ) = ( x + 1 ) × a s k ( x ) − ∑ i = 1 x d [ i ] × i Ask(x)=(x+1)\times ask(x) - \sum_{i=1}^xd[i]\times i Ask(x)=(x+1)×ask(x)−∑i=1x​d[i]×i

这时不能再展开了,只需要再开一个树状数组(或在原树状数组求和与修改的过程中计算)维护 ∑ i = 1 x d [ i ] × i \sum_{i=1}^xd[i]\times i ∑i=1x​d[i]×i 即可 (两种方案都可以)
记得差分 最终答案是 A s k ( r ) − A s k ( l − 1 ) Ask(r)-Ask(l-1) Ask(r)−Ask(l−1)
给出很短的Code:

#include<iostream>
#define int long long
using namespace std;
int c[5000001][2],a[5000001];
int n,m;
int lowbit(int x) {
	return x&(-x);
}
void add(int x,int k,int y){
	for(;x<=n;x+=lowbit(x)) c[x][y]+=k;
}
int ask(int x,int y){
	int ans=0;
	for(;x>0;x-=lowbit(x)) ans+=c[x][y];
	return ans;
}
int ask1(int k){
	return (k+1)*ask(k,0)-ask(k,1);
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		add(i,a[i]-a[i-1],0);
		add(i,(a[i]-a[i-1])*i,1);
	}
	for(int i=1;i<=m;i++){
		int opt,x,y,z;
		cin>>opt>>x>>y;
		if(opt==1){
			cin>>z;
			add(x,z,0);
			add(y+1,-z,0);
			add(x,x*z,1);
			add(y+1,-(y+1)*z,1);
		}
		if(opt==2){
			cout<<ask1(y)-ask1(x-1)<<'\n';
		}
	}
}

完结撒花 QwQ
v(。・ω・。)
如有问题,私信洛谷或CSDN
本文代码均包含在资源(审核通过后附)

标签:洛谷,int,Rot,tree,data,add,Ask,P3372,模板
来源: https://blog.csdn.net/Dark_Volcano/article/details/122758276

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有