(远古代码了,还在用指针)

线段树

​P3372 【模板】线段树 1​

​P3373 【模板】线段树 2​

注意 \(pushdown\) 操作的优先级:

儿子的值 \(=\) 此刻儿子的值 \(\times\) 爸爸的乘法 \(lazy~+\) 儿子的区间长度 \(\times\) 爸爸的加法 \(lazy\)

即:先下传 \(lazymul\) ,再下传 \(lazyadd\) 。

$\texttt{code}$

struct Tree
{
int sum,add,mul;
}tree[Maxn*4];
void pushdown(int p,int nl,int nr)
{
if(nl==nr) return;
int mid=(nl+nr)>>1,tmpm=tree[p].mul,tmpa=tree[p].add,ls=p<<1,rs=p<<1|1;
tree[ls].add=1ll*tree[ls].add*tmpm%mod,tree[rs].add=1ll*tree[rs].add*tmpm%mod;
tree[ls].mul=1ll*tree[ls].mul*tmpm%mod,tree[rs].mul=1ll*tree[rs].mul*tmpm%mod;
tree[ls].sum=1ll*tree[ls].sum*tmpm%mod,tree[rs].sum=1ll*tree[rs].sum*tmpm%mod;
tree[ls].sum=(tree[ls].sum+1ll*(mid-nl+1ll)*tmpa)%mod;
tree[rs].sum=(tree[rs].sum+1ll*(nr-mid)*tmpa)%mod;
tree[ls].add=(tree[ls].add+tmpa)%mod,tree[rs].add=(tree[rs].add+tmpa)%mod;
tree[p].add=0,tree[p].mul=1;
}
void add(int p,int nl,int nr,int l,int r,int k)
{
if(nl>=l && nr<=r)
{
tree[p].sum=(tree[p].sum+1ll*k*(nr-nl+1ll))%mod;
tree[p].add=(tree[p].add+k)%mod;
return;
}
pushdown(p,nl,nr);
int mid=(nl+nr)>>1;
if(mid>=l) add(p<<1,nl,mid,l,r,k);
if(mid<r) add(p<<1|1,mid+1,nr,l,r,k);
tree[p].sum=(tree[p<<1].sum+tree[p<<1|1].sum)%mod;
}
void mul(int p,int nl,int nr,int l,int r,int k)
{
if(nl>=l && nr<=r)
{
tree[p].mul=1ll*tree[p].mul*k%mod;
tree[p].add=1ll*tree[p].add*k%mod;
tree[p].sum=1ll*tree[p].sum*k%mod;
return;
}
pushdown(p,nl,nr);
int mid=(nl+nr)>>1;
if(mid>=l) mul(p<<1,nl,mid,l,r,k);
if(mid<r) mul(p<<1|1,mid+1,nr,l,r,k);
tree[p].sum=(tree[p<<1].sum+tree[p<<1|1].sum)%mod;
}


线段树常见玩法:

扫描线

线段树的应用 。

关于扫描线为什么不用 \(pushdown\) 操作:

这题如果一个结点在 覆盖这个区间时下传了标记 ,整棵子树都被覆盖一次,取消覆盖时要遍历整棵子树重新获取信息

$\texttt{solution}$

大致步骤:

  • 先进行离散化处理 。
  • 处理矩形的上下边界,变为一条条扫描线 。
  • 进行区间加操作 。

每个区间( 线段树上的点 )记录信息:

  • 有多少条线段 直接 将这条线段全部覆盖( 从父亲那里继承来的不能算 ) 。
  • 这个区间内被矩形覆盖到的实际宽度之和 。

注意:

  • 进行区间加操作时的下标是离散化后的下标,而统计的答案是实际大小 。
  • 空间限制宽泛,当算不清楚到底要开多大时尽量往打了开 。

大致过程如图所示:

线段树_扫描线

线段树_#define_02

线段树_数据结构_03

线段树_扫描线_04

线段树_i++_05

线段树_#define_06

$\texttt{code}$

#include<bits/stdc++.h>
using namespace std;
#define Maxn 200005
typedef long long ll;
ll n,tot,l,r,ans,cnt;
ll X[Maxn][2],Y[Maxn][2],True[Maxn*2];
struct Dis { ll num,val; } tmp[Maxn*2];
struct Seg { ll xl,xr,h,val; } seg[Maxn*2];
struct Tree
{
ll sum;
ll Lazy;
}tree[Maxn*8];
bool cmp1(Dis x,Dis y) { return x.val<y.val; }
bool cmp2(Seg x,Seg y) { return x.h<y.h; }
void pushup(ll p,ll nl,ll nr)
{
if(tree[p].Lazy) tree[p].sum=True[nr+1]-True[nl];
else tree[p].sum=tree[p<<1].sum+tree[p<<1|1].sum;
}
void add(ll p,ll nl,ll nr,ll k)
{
if(nl>=l && nr<=r)
{
tree[p].Lazy+=k;
pushup(p,nl,nr);
return;
}
ll mid=(nl+nr)>>1;
if(mid>=l) add(p<<1,nl,mid,k);
if(mid<r) add(p<<1|1,mid+1,nr,k);
pushup(p,nl,nr);
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
scanf("%lld",&n);
for(ll i=1;i<=n;i++) scanf("%lld%lld%lld%lld",&X[i][0],&Y[i][0],&X[i][1],&Y[i][1]);
for(ll i=1;i<=n;i++) tmp[i*2-1]=(Dis){i*2-1,X[i][1]},tmp[i*2]=(Dis){i*2,X[i][0]};
sort(tmp+1,tmp+n*2+1,cmp1);
tmp[0].val=-1;
for(ll i=1;i<=n*2;i++)
{
if(tmp[i].val!=tmp[i-1].val) True[++cnt]=tmp[i].val;
X[(tmp[i].num+1)/2][tmp[i].num%2]=cnt;
} // 离散化
for(ll i=1;i<=n;i++)
seg[++tot]=(Seg){X[i][0],X[i][1],Y[i][0],1},
seg[++tot]=(Seg){X[i][0],X[i][1],Y[i][1],-1};
sort(seg+1,seg+tot+1,cmp2); // 以上是预处理部分
for(ll i=1;i<=tot;i++)
{
ans=ans+1ll*(seg[i].h-seg[i-1].h)*tree[1].sum;
l=seg[i].xl,r=seg[i].xr-1;
add(1,1,cnt,seg[i].val);
}
printf("%lld\n",ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}


$\texttt{solution}$

其他部分与扫描线模板一样,就是统计的时候记录这个区间被分为多少个不相交的线段,另外进行两次扫描线( 横着一次,竖着一次 ) 。

如何记录被分为多少个线段:

  • 记录的值:( 一条线段 \([l,r]\) 实际表示 \([True_l,True_{r+1}-1]\) ,这同扫描线模板一样 )
  • \(tag\) : 整个区间被整体覆盖了几次( 不下传 )
  • \(sum\) : 整个区间被几条互不相交的线段覆盖
  • \(Coverl\) : 左端点是否被覆盖( 合并用 )
  • \(Coverr\) : 右端点是否被覆盖( 合并用 )
  • \(pushup\) 操作

$\texttt{code}$

#include<bits/stdc++.h>
using namespace std;
#define infll 0x7f7f7f7f7f7f7f7f
#define inf 0x7f7f7f7f
#define Maxn 40005
typedef long long ll; // 习惯性开大空间
ll maxll(ll x,ll y){ return x>y?x:y; }
ll minll(ll x,ll y){ return x<y?x:y; }
int n,l,r,cntx,cnty,tot;
int Truex[Maxn*2],Truey[Maxn*2],X[Maxn][2],Y[Maxn][2];
ll ans=0;
struct Dis { int num,val; } Inx[Maxn*2],Iny[Maxn*2];
struct Seg { int nl,nr,h,val; } segx[Maxn*2],segy[Maxn*2];
bool cmp1(Dis x,Dis y){ return x.val<y.val; }
bool cmp2(Seg x,Seg y){ return x.h<y.h; }
struct Tree
{
int sum,tag,Coverl,Coverr;
}tree[Maxn*16];
void workpre()
{
scanf("%d",&n),tot=n*2;
for(int i=1;i<=n;i++) scanf("%d%d%d%d",&X[i][0],&Y[i][0],&X[i][1],&Y[i][1]);
for(int i=1;i<=n;i++) Inx[i*2-1]=(Dis){i*2-1,X[i][1]},Inx[i*2]=(Dis){i*2,X[i][0]};
for(int i=1;i<=n;i++) Iny[i*2-1]=(Dis){i*2-1,Y[i][1]},Iny[i*2]=(Dis){i*2,Y[i][0]};
sort(Inx+1,Inx+tot+1,cmp1),sort(Iny+1,Iny+tot+1,cmp1);
Inx[0].val=Iny[0].val=-inf;
for(int i=1;i<=tot;i++)
{
if(Inx[i].val!=Inx[i-1].val) Truex[++cntx]=Inx[i].val;
if(Iny[i].val!=Iny[i-1].val) Truey[++cnty]=Iny[i].val;
X[(Inx[i].num+1)/2][Inx[i].num%2]=cntx;
Y[(Iny[i].num+1)/2][Iny[i].num%2]=cnty;
}
for(int i=1;i<=n;i++)
{
segx[i*2-1]=(Seg){X[i][0],X[i][1],Y[i][0],1};
segx[i*2]=(Seg){X[i][0],X[i][1],Y[i][1],-1};
segy[i*2-1]=(Seg){Y[i][0],Y[i][1],X[i][0],1};
segy[i*2]=(Seg){Y[i][0],Y[i][1],X[i][1],-1};
}
sort(segx+1,segx+tot+1,cmp2),sort(segy+1,segy+tot+1,cmp2);
}
void build(int p,int nl,int nr)
{
tree[p].sum=tree[p].tag=tree[p].Coverl=tree[p].Coverr=0;
if(nl==nr) return;
int mid=(nl+nr)>>1;
build(p<<1,nl,mid),build(p<<1|1,mid+1,nr);
}
void pushup(int p,int nl,int nr)
{
if(tree[p].tag) tree[p].sum=tree[p].Coverl=tree[p].Coverr=1;
else if(nl==nr) tree[p].sum=tree[p].Coverl=tree[p].Coverr=0;
else
{
tree[p].sum=tree[p<<1].sum+tree[p<<1|1].sum;
if(tree[p<<1].Coverr && tree[p<<1|1].Coverl) tree[p].sum--;
tree[p].Coverl=tree[p<<1].Coverl;
tree[p].Coverr=tree[p<<1|1].Coverr;
}
}
void add(int p,int nl,int nr,int k)
{
if(nl>=l && nr<=r)
{
tree[p].tag+=k;
pushup(p,nl,nr);
return;
}
int mid=(nl+nr)>>1;
if(mid>=l) add(p<<1,nl,mid,k);
if(mid<r) add(p<<1|1,mid+1,nr,k);
pushup(p,nl,nr);
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
workpre(); // 输入 + 初始化
for(int i=1;i<tot;i++)
{
l=segx[i].nl,r=segx[i].nr-1;
add(1,1,cntx,segx[i].val);
ans+=2ll*tree[1].sum*(Truey[segx[i+1].h]-Truey[segx[i].h]);
}
build(1,1,cnty);
for(int i=1;i<tot;i++)
{
l=segy[i].nl,r=segy[i].nr-1;
add(1,1,cnty,segy[i].val);
ans+=2ll*tree[1].sum*(Truex[segy[i+1].h]-Truex[segy[i].h]);
}
printf("%lld\n",ans);
//fclose(stdin);
//fclose(stdout);
return 0;
}


区间最长的 XX

比如最长的由 \(0\) 组成的序列 。

可以在每一个节点存储:

  • 从区间最左端开始的最长序列
  • 从区间最右端开始的最长序列
  • 整个区间的最长序列

合并时参考代码 。

例题:

​P6492 [COCI2010-2011#6] STEP​

​P2894 [USACO08FEB]Hotel G​

struct Tree
{
int ml,mr,ans;
}tree[Maxn<<2];
void pushup(int p,int nl,int nr)
{
int mid=(nl+nr)>>1;
tree[p].ml=tree[p<<1].ml,tree[p].mr=tree[p<<1|1].mr;
if(tree[p<<1].ml==mid-nl+1) tree[p].ml=tree[p<<1].ml+tree[p<<1|1].ml;
if(tree[p<<1|1].mr==nr-mid) tree[p].mr=tree[p<<1].mr+tree[p<<1|1].mr;
tree[p].ans=max(max(tree[p<<1].ans,tree[p<<1|1].ans),tree[p<<1].mr+tree[p<<1|1].ml);
}
void build(int p,int nl,int nr)
{
if(nl==nr) { tree[p].ml=tree[p].mr=tree[p].ans=1; return; }
int mid=(nl+nr)>>1;
if(mid>=nl) build(p<<1,nl,mid);
if(mid<nr) build(p<<1|1,mid+1,nr);
pushup(p,nl,nr);
}