【数据结构】线段树进阶(乘法/根号线段树)

感谢博主:https://www.cnblogs.com/jason2003/p/9676729.html

板块一:乘法线段树:
如果这个线段树只有乘法,那么直接加入lazytage变成乘,然后tree[i].sum*=k就好了。但是,如果我们是又加又乘,那就不一样了。

当lazytage下标传递的时候,我们需要考虑,是先加再乘还是先乘再加。我们只需要对lazytage做这样一个处理。

lazytage分为两种,分别是加法的plz和乘法的mlz。
mlz很简单处理,pushdown时直接父亲的就可以了,那么加法呢?
我们需要把(原先的plz
父亲的mlz)再加上父亲的plz.

来道板子题方便理解吧:洛谷P3373

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;

ll input[500010];

struct node{
	ll l,r,sum,plz,mlz;
}tree[2000040];

int n,m,p;

inline void build(ll i,ll l,ll r){
	tree[i].l=l;tree[i].r=r,tree[i].plz=0;tree[i].mlz=1;
	if(l==r){
		tree[i].sum=input[l]%p;
		return ;
	}
	ll mid=(l+r)>>1;
	build(2*i,l,mid);
	build(2*i+1,mid+1,r);
	tree[i].sum=(tree[2*i].sum+tree[i*2+1].sum)%p;
}

inline void push_down(ll i)
{
	ll k1=tree[i].mlz,k2=tree[i].plz;
	tree[2*i].sum=(tree[2*i].sum*k1+k2*(tree[2*i].r-tree[2*i].l+1))%p;
	tree[2*i+1].sum=(tree[2*i+1].sum*k1+k2*(tree[2*i+1].r-tree[2*i+1].l+1))%p;	
	tree[2*i].mlz=(k1*tree[2*i].mlz)%p;
	tree[2*i+1].mlz=(k1*tree[2*i+1].mlz)%p;	
	tree[2*i].plz=(k1*tree[2*i].plz+k2)%p;
	tree[2*i+1].plz=(k1*tree[2*i+1].plz+k2)%p;
	tree[i].plz=0;
	tree[i].mlz=1;
	return ;
}

inline void mul(ll i,ll l,ll r,ll k){
    if(tree[i].r<l || tree[i].l>r)  return ;
	if(tree[i].l>=l&&tree[i].r<=r){
		tree[i].sum=(tree[i].sum*k)%p;
		tree[i].mlz=(tree[i].mlz*k)%p;
		tree[i].plz=(tree[i].plz*k)%p;
		return ;
	}	
	push_down(i);
	if(tree[2*i].r>=l) mul(2*i,l,r,k);
	if(tree[2*i+1].l<=r) mul(2*i+1,l,r,k);
	tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%p;
}

inline void add(ll i,ll l,ll r,ll k)
{
    if(tree[i].r<l || tree[i].l>r)  return ;
	if(tree[i].r<=r && tree[i].l>=l)//区间修改 
    {
        tree[i].sum+=(k*(tree[i].r-tree[i].l+1))%p;
        tree[i].plz=(tree[i].plz+k)%p;
        return ;
    }
    push_down(i);
    if(tree[i*2].r>=l)
        add(i*2,l,r,k);
    if(tree[i*2+1].l<=r)
        add(i*2+1,l,r,k);
    tree[i].sum=(tree[i*2].sum+tree[i*2+1].sum)%p;
}



inline ll search(ll i,ll l,ll r){
    if(tree[i].l>=l && tree[i].r<=r) //查询 
        return tree[i].sum;
    if(tree[i].r<l || tree[i].l>r)  return 0;
    push_down(i);
    ll s=0;
    if(tree[i*2].r>=l)  s+=search(i*2,l,r)%p;
    if(tree[i*2+1].l<=r)  s+=search(i*2+1,l,r)%p;
    return s%p;
}

int main(){
	IO;
	cin>>n>>m>>p;
	for(int i=1;i<=n;i++) cin>>input[i];
	build(1,1,n);
	while(m--){
		int t;cin>>t;
		if(t==1){
			ll l,r,k;cin>>l>>r>>k;
			mul(1,l,r,k);
		}
		if(t==2){
			ll l,r,k;cin>>l>>r>>k;
			add(1,l,r,k);
		}
		if(t==3){
			ll l,r;cin>>l>>r;
			cout<<search(1,l,r)<<endl;
		}
	}
}

然后加法和减法的函数同理,维护lazytage的时候加法标记一定要记得现乘再加。

值得一提的是,计算*2时一定要改成i<<1这样能解决很多时间,还有要开long long,还有,函数前面要加inline 我在其他OJ交这道题时,就因为没加inline 就被卡了,交了就过了。

版块二:根号线段树:
其实,根号线段树和除法线段树一样。她们乍眼一看感觉直接用lazytage标记除了多少,但是实际上,会出现精度问题。

c++的除法是向下取整,很明显,(a+b)/k!=a/k+b/k(在向下取整的情况下),而根号,很明显根号(a)+根号(b)!=根号(a+b)那么怎么办?

第一个想法就是暴力,对于每个要改动的区间l~r,把里面的每个点都单独除,但这样就会把时间复杂度卡得比大暴力都慢(因为多个常数),所以怎么优化?

我们对于每个区间,维护她的最大值和最小值,然后每次修改时,如果这个区间的最大值根号和最小值的根号一样,说明这个区间整体根号不会产生误差,就直接修改(除法同理)

其中,lazytage把除法当成减法,记录的是这个区间里每个元素减去的值。

下面是根号线段树的模板 BZOJ3211(高精度)

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;

ll input[500010];

struct node{
	ll l,r,sum,lz,minn,maxx;
}tree[2000040];

int n,m,p;

inline void build(ll i,ll l,ll r){
	tree[i].l=l;tree[i].r=r;
	if(l==r){
		tree[i].sum=tree[i].minn=tree[i].maxx=input[l];
		return ;
	}
	ll mid=(l+r)>>1;
	build(2*i,l,mid);
	build(2*i+1,mid+1,r);
	tree[i].sum=tree[2*i].sum+tree[i*2+1].sum;
	tree[i].minn=min(tree[2*i].minn,tree[2*i+1].minn);
	tree[i].maxx=max(tree[2*i].maxx,tree[2*i+1].maxx);	
}

inline void push_down(ll i)
{
	if(!tree[i].lz) return ;
	ll k=tree[i].lz;
	tree[2*i].lz+=k;
	tree[2*i+1].lz+=k;
    tree[i*2].sum-=(tree[i*2].r-tree[i*2].l+1)*k;
    tree[i*2+1].sum-=(tree[i*2+1].r-tree[i*2+1].l+1)*k;
    tree[i*2].minn-=k;
    tree[i*2+1].minn-=k;
    tree[i*2].maxx-=k;
    tree[i*2+1].maxx-=k;
    tree[i].lz=0;
    return ;
}

inline void Sqrt(int i,int l,int r){
    if(tree[i].l>=l && tree[i].r<=r && (tree[i].minn-(long long)sqrt(tree[i].minn))==(tree[i].maxx-(long long)sqrt(tree[i].maxx))){
        long long u=tree[i].minn-(long long)sqrt(tree[i].minn);
        tree[i].lz+=u;
        tree[i].sum-=(tree[i].r-tree[i].l+1)*u;
        tree[i].minn-=u;
        tree[i].maxx-=u;
        return ;
    }
    if(tree[i].r<l || tree[i].l>r)  return ;
    push_down(i);
    if(tree[i*2].r>=l)  Sqrt(i*2,l,r);
    if(tree[i*2+1].l<=r)  Sqrt(i*2+1,l,r);
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;
    tree[i].minn=min(tree[i*2].minn,tree[i*2+1].minn);
    tree[i].maxx=max(tree[i*2].maxx,tree[i*2+1].maxx);
    return ;
}


inline ll search(ll i,ll l,ll r){
    if(tree[i].l>=l && tree[i].r<=r) //查询 
        return tree[i].sum;
    if(tree[i].r<l || tree[i].l>r)  return 0;
    push_down(i);
    ll s=0;
    if(tree[i*2].r>=l)  s+=search(i*2,l,r);
    if(tree[i*2+1].l<=r)  s+=search(i*2+1,l,r);
    return s;
}

int main(){
	IO;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>input[i];
	cin>>m;
	build(1,1,n);
	while(m--){
		int t,l,r;cin>>t>>l>>r;
		if(t==1){
			cout<<search(1,l,r)<<endl;
		}
		if(t==2){
			Sqrt(1,l,r);
		}
	}
}

HDU4027(低精度+剪枝)

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,n,a) for(int i=n;i>=a;i--)
#define endl '\n'
#define mem(a) memset(a,0,sizeof(a))
#define IO ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;


const int maxn=100000+10;
ll input[maxn];

struct node{
    ll l,r,sum;
}tree[maxn*4];
    
int n,m,p;

inline void build(ll i,ll l,ll r){
    tree[i].l=l;tree[i].r=r;
    if(l==r){
        tree[i].sum=input[l];
        return ;
    }
    ll mid=(l+r)>>1;
    build(2*i,l,mid);
    build(2*i+1,mid+1,r);
    tree[i].sum=tree[2*i].sum+tree[i*2+1].sum;
}


inline void Sqrt(int i,int l,int r){
    if(tree[i].l==tree[i].r){
        tree[i].sum=sqrt(tree[i].sum);
        return ;
    }
    if(tree[i].l>r||tree[i].r<l) return ;
    if(tree[i].l>=l&&tree[i].r<=r&&tree[i].sum==(tree[i].r-tree[i].l+1)) return ;
    //剪枝
    ll mid=(tree[i].l+tree[i].r)>>1;
    if(tree[i<<1].r>=l)  Sqrt(i<<1,l,r);
    if(tree[i<<1|1].l<=r)  Sqrt(i<<1|1,l,r);
    tree[i].sum=tree[i<<1].sum+tree[i<<1|1].sum;      
    
    
}


inline ll search(ll i,ll l,ll r){
    if(tree[i].l>=l && tree[i].r<=r) 
        return tree[i].sum;
    if(tree[i].r<l || tree[i].l>r)  return 0;
    ll s=0;
    if(tree[i*2].r>=l)  s+=search(i*2,l,r);
    if(tree[i*2+1].l<=r)  s+=search(i*2+1,l,r);
    return s;
}

int main(){
    ll c=1;
    while(~scanf("%lld",&n)){
        for(int i=1;i<=n;i++) scanf("%lld",&input[i]);
        cin>>m;
        build(1,1,n);
        printf("Case #%lld:\n", c++);                
        while(m--){
            ll t,l,r;
            scanf("%lld%lld%lld",&t,&l,&r);
            if(l>r) swap(l,r);
            if(t==1){
                printf("%lld\n",search(1,l,r));
            }
            if(t==0){
                Sqrt(1,l,r);
            }
        }
        puts("");
    }
}
发布了71 篇原创文章 · 获赞 5 · 访问量 3393

猜你喜欢

转载自blog.csdn.net/Rainfoo/article/details/104020371
今日推荐