【洛谷题单刷题日记】线段树

1:洛谷 P3372 【模板】线段树 1

  线段树是用来解决区间和/区间最值/区间覆盖的问题,而本道题涉及到了区间和问题,是区间修改和区间查询的问题(单点查询和单点修改对应的就是 l = = r l==r l==r的情况)。如果修改一个区间时,每次都修改到叶结点,那么一个叶节点的修改所需的复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),修改一次区间的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn), Q Q Q次区间修改的时间复杂度是 O ( n 2 l o g n ) O(n^2logn) O(n2logn),效率甚至低于暴力。
  因此,这里采用lazy-tag的形式,即修改仅针对区间整体进行操作,不去修改内部元素的具体内容,只有当区间的一致性被破坏时才把变化值传递给子区间,这样就可以维持一次操作的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn).

#include
#define close ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) 
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
ll n,m,mark[maxn*4],tree[maxn*4],A[maxn];
void push_down(ll p,ll len)
{
	mark[2*p]+=mark[p];mark[2*p+1]+=mark[p];
	tree[2*p]+=mark[p]*(len-len/2);
	tree[2*p+1]+=mark[p]*(len/2);
	mark[p]=0;
}
void build(ll l=1,ll r=n,ll p=1)
{
	if(l==r) tree[p]=A[l];
	else{
		ll mid=(l+r)/2;
		build(l,mid,2*p);
		build(mid+1,r,2*p+1);
		tree[p]=tree[p*2]+tree[p*2+1];
	}
}
void update(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=n)
{
	if(l>cr || r<cl) return;
	if(l<=cl && cr<=r) 
	{
		tree[p]+=d*(cr-cl+1);
		if(cr>cl) mark[p]+=d;
	}  
	else{
		ll mid=(cl+cr)/2;
		push_down(p,cr-cl+1);
		update(l,r,d,p*2,cl,mid);
		update(l,r,d,p*2+1,mid+1,cr);
		tree[p]=tree[p*2]+tree[p*2+1]; 
	} 
}
ll query(ll l,ll r,ll p=1,ll cl=1,ll cr=n)
{
	if(l>cr || r<cl) return 0;
	if(l<=cl && cr<=r) return tree[p];
	else{
		ll mid=(cl+cr)/2;
		push_down(p,cr-cl+1);
		return query(l,r,2*p,cl,mid)+query(l,r,2*p+1,mid+1,cr);
	}
}
int main()
{
	close;cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>A[i];
	build();
	while(m--)
	{
		int op;cin>>op;
		if(op==1){
			int x,y,k;cin>>x>>y>>k;
			update(x,y,k); 
		}
		else{
			int x,y;cin>>x>>y;
			cout<<query(x,y)<<endl;
		}
	}
}

2:洛谷 P3373 【模板】线段树 2

  这个题相较于上一题的难点在于,怎么样进行lazy-tag的传递,需要注意加减法的先后顺序问题。

#include
#define close ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) 
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
ll n,m,mod,markadd[maxn*4],markmul[maxn*4],tree[maxn*4],A[maxn];
void push_down(ll p,ll len)
{	
	tree[2*p]=(tree[2*p]*markmul[p]%mod+markadd[p]*(len-len/2)%mod)%mod;
	tree[2*p+1]=(tree[2*p+1]*markmul[p]%mod+markadd[p]*(len/2)%mod)%mod;
	
	markmul[2*p]=markmul[2*p]*markmul[p]%mod;
	markmul[2*p+1]=markmul[2*p+1]*markmul[p]%mod;
	
	markadd[2*p]=(markadd[2*p]*markmul[p]%mod+markadd[p])%mod;
	markadd[2*p+1]=(markadd[2*p+1]*markmul[p]%mod+markadd[p])%mod;
	
	markadd[p]=0;markmul[p]=1;
}
void build(ll l=1,ll r=n,ll p=1)
{
	if(l==r) tree[p]=A[l]%mod,markmul[l]=1;
	else{
		ll mid=(l+r)/2;
		build(l,mid,2*p);
		build(mid+1,r,2*p+1);
		tree[p]=(tree[p*2]+tree[p*2+1])%mod;
		markmul[p]=1;
	}
}
void update_add(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=n)
{
	if(l>cr || r<cl) return;
	if(l<=cl && cr<=r) 
	{
		tree[p]=(tree[p]+d*(cr-cl+1)%mod)%mod;
		if(cr>cl) markadd[p]=(markadd[p]+d)%mod;
	}  
	else{
		ll mid=(cl+cr)/2;
		push_down(p,cr-cl+1);
		update_add(l,r,d,p*2,cl,mid);
		update_add(l,r,d,p*2+1,mid+1,cr);
		tree[p]=(tree[p*2]+tree[p*2+1])%mod; 
	} 
}
void update_mul(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=n)
{
	if(l>cr || r<cl) return;
	if(l<=cl && cr<=r) 
	{
		tree[p]=d*tree[p]%mod;
		if(cr>cl) markmul[p]=markmul[p]*d%mod,markadd[p]=markadd[p]*d%mod;
	}  
	else{
		ll mid=(cl+cr)/2;
		push_down(p,cr-cl+1);
		update_mul(l,r,d,p*2,cl,mid);
		update_mul(l,r,d,p*2+1,mid+1,cr);
		tree[p]=(tree[p*2]+tree[p*2+1])%mod; 
	} 
}
ll query(ll l,ll r,ll p=1,ll cl=1,ll cr=n)
{
	if(l>cr || r<cl) return 0;
	if(l<=cl && cr<=r) return tree[p]%mod;
	else{
		ll mid=(cl+cr)/2;
		push_down(p,cr-cl+1);
		return (query(l,r,2*p,cl,mid)+query(l,r,2*p+1,mid+1,cr))%mod;
	}
}
int main()
{
	close;cin>>n>>m>>mod;
	for(int i=1;i<=n;++i) cin>>A[i];
	build();
	while(m--)
	{
		int op;cin>>op;
		if(op==1){
			int x,y,k;cin>>x>>y>>k;
			update_mul(x,y,k); 
		}
		else if(op==2){
			int x,y,k;cin>>x>>y>>k;
			update_add(x,y,k); 
		}
		else{
			int x,y;cin>>x>>y;
			cout<<query(x,y)<<endl;
		}
	}
}

3:P5490 【模板】扫描线

  求面积并应该是线段树一个非常重要的应用,使用了离散化的思想。核心的转化在于求解方式是 ∑ 截 线 段 的 长 度 ∗ 扫 过 的 面 积 \sum 截线段的长度*扫过的面积 线(参考的博客讲解:https://zhuanlan.zhihu.com/p/103616664)

#include
using namespace std;
typedef long long ll;
const int maxn=2e5+100;
struct ScanLine{
    ll x,y1,y2,mark;
    ScanLine(){}
    ScanLine(ll x,ll y1,ll y2,ll mark){this->x=x;this->y1=y1;this->y2=y2;this->mark=mark;}
    bool operator < (const ScanLine& a)const {return x<a.x;}
}line[maxn*2];
ll recy[maxn*2],cover[maxn*4],length[maxn*4];
ll tot;
void Push_down(ll p,ll l,ll r)
{
    if(cover[p]) length[p]=recy[r]-recy[l];
    else if(l+1==r) length[p]=0;
    else length[p]=length[p*2]+length[p*2+1];
}
void Update(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=tot)
{
    if(l>cr || r<cl) return;
    if(cl>=l && cr<=r) {cover[p]+=d;Push_down(p,cl,cr);return;}
    if(cl+1==cr) return;
    else{
        int mid=(cl+cr)/2;
        Update(l,r,d,p*2,cl,mid);
        Update(l,r,d,p*2+1,mid,cr);
        Push_down(p,cl,cr);
    }
}
int main()
{
	int n;scanf("%d",&n);int cnt=0;
    for(int i=1;i<=n;++i)
    {
        ll x1,x2,y1,y2;
        scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
        line[++cnt]=ScanLine(x1,y1,y2,1);recy[cnt]=y1;
        line[++cnt]=ScanLine(x2,y1,y2,-1);recy[cnt]=y2;
    }
    memset(length,0,sizeof(length));
    memset(cover,0,sizeof(cover));
    sort(line+1,line+cnt+1);sort(recy+1,recy+cnt+1);
    tot=unique(recy+1,recy+cnt+1)-(recy+1);
    ll ans=0;
    for(int i=1;i<cnt;++i)
    {
        int y_down=lower_bound(recy+1,recy+tot+1,line[i].y1)-recy;
        int y_up=lower_bound(recy+1,recy+tot+1,line[i].y2)-recy;
        Update(y_down,y_up,line[i].mark);
        ans+=length[1]*(line[i+1].x-line[i].x);
    }
    printf("%lld",ans);
}

4:洛谷 P4588 [TJOI2018]数学计算

  这道题表面上看着像一个模拟,但是做除法取模的时候我们要乘上他的逆元,这是模拟的时间复杂度的主要来源。但我们可以把每次操作视为一个叶结点,操作一就是把当次操作的乘数改为 n u m num num,操作二就是把操作 p o s pos pos的乘数改为1,因此每次的操作都是单点修改;然后每次输出的答案就是所有叶子结点的乘数之积,也就是区间查询。

#include
using namespace std;
typedef long long ll;
const int maxn=1e5+100;
ll Q,mod,tree[maxn*4];
void Update(ll l,ll r,ll d,ll p=1,ll cl=1,ll cr=Q)
{
    if(l>cr||r<cl) return;
    if(cl>=l && cr<=r) tree[p]=d;
    else{
        ll mid=(cl+cr)>>1;
        Update(l,r,d,p*2,cl,mid);
        Update(l,r,d,p*2+1,mid+1,cr);
        tree[p]=tree[p*2]*tree[p*2+1]%mod;
    }
}
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        for(int i=1;i<maxn*4;++i) tree[i]=1;
        scanf("%lld%lld",&Q,&mod);
        for(int i=1;i<=Q;++i)
        {
            int op;ll num;scanf("%d%lld",&op,&num);
            if(op==1){Update(i,i,num);printf("%lld\n",tree[1]%mod);}
            else{Update(num,num,1);printf("%lld\n",tree[1]%mod);}
        }
    }
}

5:洛谷 P1502 窗口的星星

你可能感兴趣的:(【洛谷题单刷题日记】线段树)