基础线段树及常见应用(扫描线、优化建图、线段树分治……)

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

线段树模板

P3372 【模板】线段树 1

P3373 【模板】线段树 2

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

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

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

$ exttt{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) 操作:

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

$ exttt{solution}$

大致步骤:

  • 先进行离散化处理 。

  • 处理矩形的上下边界,变为一条条扫描线 。

  • 进行区间加操作 。

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

  • 有多少条线段 直接 将这条线段全部覆盖( 从父亲那里继承来的不能算 ) 。

  • 这个区间内被矩形覆盖到的实际宽度之和 。

注意:

  • 进行区间加操作时的下标是离散化后的下标,而统计的答案是实际大小 。

  • 空间限制宽泛,当算不清楚到底要开多大时尽量往打了开 。

大致过程如图所示:

$ exttt{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
",ans);
     //fclose(stdin);
     //fclose(stdout);
     return 0;
}
$ exttt{solution}$

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

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

  • 记录的值:( 一条线段 ([l,r]) 实际表示 ([True_l,True_{r+1}-1]) ,这同扫描线模板一样 )

    • (tag) : 整个区间被整体覆盖了几次( 不下传 )

    • (sum) : 整个区间被几条互不相交的线段覆盖

    • (Coverl) : 左端点是否被覆盖( 合并用 )

    • (Coverr) : 右端点是否被覆盖( 合并用 )

  • (pushup) 操作

$ exttt{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
",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);
}

线段树优化建图

CF786B Legacy

描述:有 (n(nle 10^5)) 个点,每次给出一些边 u->vu->[l,r][l,r]->v,求出 (s)(t) 的最短路。

由于 (n) 超级大,(n^2) 建图肯定会 T,因此有了线段树优化建图。

(感谢 tzc_wk 制作的图片)

我们将区间放在线段树上,即 out 树和 in 树,分别表示两种连边操作,将每个区间分为不超过 (log n) 个小区间,分别建立虚点并连边。

注意在两棵树的底层每对点之间都连上了长度为 (0) 的边,不然这个路径只会经过一条边。

我们在 id 与 id 之间连边,千万不能用线段树的下标来处理!!

特别注意——关于空间:由于线段树的一系列操作,所有点都需要开到至少 $4 imes $【树的个数】倍大小,而边数一棵树就需要 (4) 被空间,加上后来连上的一些边,需要开到 (32)(64) 倍。

CF786B 模板代码
// Author:A weak man named EricQian
#include<bits/stdc++.h>
using namespace std;
#define infll 0x7f7f7f7f7f7f7f7f
#define inf 0x3f3f3f3f
#define Maxn 100005  //  空间应该开多大? 
#define pa pair<ll,int>
#define fi first
#define se second
typedef long long ll;
inline int rd()
{
	 int x=0;
	 char ch,t=0;
	 while(!isdigit(ch = getchar())) t|=ch=='-';
	 while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
	 return x=t?-x:x;
}
inline ll maxll(ll x,ll y){ return x>y?x:y; }
inline ll minll(ll x,ll y){ return x<y?x:y; }
inline ll absll(ll x){ return x>0ll?x:-x; }
inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); }
int n,q,s,id,tot;
int hea[Maxn<<3],nex[Maxn<<5],ver[Maxn<<5];
int indot[Maxn<<3],outdot[Maxn<<3],numin[Maxn<<3],numout[Maxn<<3];
ll ds[Maxn<<3],edg[Maxn<<5];
bool vis[Maxn<<3];
inline void add_edge(int x,int y,ll d){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot,edg[tot]=d; }
void build(int p,int nl,int nr)
{
	 if(nl==nr) { numout[p]=nl,numin[p]=nl+n; return; }
	 numout[p]=++id,numin[p]=++id;
	 int mid=(nl+nr)>>1;
	 build(p<<1,nl,mid),build(p<<1|1,mid+1,nr);
	 add_edge(numout[p<<1],numout[p],0),add_edge(numout[p<<1|1],numout[p],0);
	 add_edge(numin[p],numin[p<<1],0),add_edge(numin[p],numin[p<<1|1],0);
}
void add_out_tree(int p,int nl,int nr,int l,int r,int x,int w)
{
	 if(nl>=l && nr<=r) { add_edge(numout[p],x,w); return; }
	 int mid=(nl+nr)>>1;
	 if(mid>=l) add_out_tree(p<<1,nl,mid,l,r,x,w);
	 if(mid<r) add_out_tree(p<<1|1,mid+1,nr,l,r,x,w);
}
void add_in_tree(int p,int nl,int nr,int l,int r,int x,int w)
{
	 if(nl>=l && nr<=r) { add_edge(x,numin[p],w); return; }
	 int mid=(nl+nr)>>1;
	 if(mid>=l) add_in_tree(p<<1,nl,mid,l,r,x,w);
	 if(mid<r) add_in_tree(p<<1|1,mid+1,nr,l,r,x,w);
}
inline void dij()
{
	 priority_queue<pa> q;
	 for(int i=1;i<=id;i++) ds[i]=infll;
	 ds[s]=0,q.push(pa(0,s));
	 while(!q.empty())
	 {
	 	 int cur=q.top().se; q.pop();
	 	 if(vis[cur]) continue;
	 	 vis[cur]=true;
	 	 for(int i=hea[cur];i;i=nex[i]) if(ds[ver[i]]>ds[cur]+edg[i])
	 	 	 ds[ver[i]]=ds[cur]+edg[i],q.push(pa(-ds[ver[i]],ver[i]));
	 }
}
int main()
{
	 //ios::sync_with_stdio(false); cin.tie(0);
	 //freopen(".in","r",stdin);
	 //freopen(".out","w",stdout);
	 n=rd(),q=rd(),s=rd(),id=n*2,build(1,1,n);
	 for(int i=1;i<=n;i++) outdot[i]=i;
	 for(int i=1;i<=n;i++) indot[i]=i+n; // 记得最终答案在 i+n  
	 for(int i=1;i<=n;i++) add_edge(i+n,i,0),add_edge(i,i+n,0);
	 // out -> in
	 for(int i=1,opt,x,l,r,w;i<=q;i++)
	 {
	 	 opt=rd();
	 	 if(opt==1) l=rd(),r=rd(),w=rd(),add_edge(outdot[l],outdot[r],w);
	 	 else if(opt==2) x=rd(),l=rd(),r=rd(),w=rd(),add_in_tree(1,1,n,l,r,x,w);
	 	 else x=rd(),l=rd(),r=rd(),w=rd(),add_out_tree(1,1,n,l,r,x+n,w);
	 }
	 dij();
	 for(int i=1;i<=n;i++) printf("%lld%c",(ds[i+n]==infll)?-1:ds[i+n],(i==n)?'
':' ');
	 //fclose(stdin);
	 //fclose(stdout);
	 return 0;
}

P3588 [POI2015] PUS

题意:给定长度为 (n) 的序列,其中有若干个点已经有了权值,其他没有。又有 (m) 个区间,要求区间内选择的 (k_i) 个点的权值严格大于其他 (len-k) 个点,要求输出一种合法的方案,或者判定不可能实现。

困难的是要求区间内选择了 (k_i) 个点,而这些点将整个区间分为了若干段。

考虑对于每个区间建立一个新的点,从每个子区间向这个点连边,并从这个点向选中的每一个点连边。

从 out 树出发,用拓扑排序可以求出每一个点的最小权值。而如果不能完成拓扑,则表明不合法。

$ exttt{code}$
// Author:A weak man named EricQian
#include<bits/stdc++.h>
using namespace std;
#define infll 0x7f7f7f7f7f7f7f7f
#define inf 0x3f3f3f3f
#define Maxn 10000005
typedef long long ll;
inline int rd()
{
	 int x=0;
	 char ch,t=0;
	 while(!isdigit(ch = getchar())) t|=ch=='-';
	 while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
	 return x=t?-x:x;
}
inline ll maxll(ll x,ll y){ return x>y?x:y; }
inline ll minll(ll x,ll y){ return x<y?x:y; }
inline ll absll(ll x){ return x>0ll?x:-x; }
inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); }
int n,s,m,id,tot;
int hea[Maxn],nex[Maxn],ver[Maxn],edg[Maxn];
int ind[Maxn],num[Maxn],r[Maxn],ds[Maxn];
inline void add_edge(int x,int y,int d){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot,edg[tot]=d,ind[y]++; }
void build(int p,int nl,int nr)
{
	 if(nl==nr) { num[p]=nl; return; }
	 num[p]=++id;
	 int mid=(nl+nr)>>1;
	 build(p<<1,nl,mid),build(p<<1|1,mid+1,nr);
	 add_edge(num[p<<1],num[p],0),add_edge(num[p<<1|1],num[p],0);
}
void change(int p,int nl,int nr,int l,int r,int x)
{
	 if(nl>=l && nr<=r) { add_edge(num[p],x,0); return; }
	 int mid=(nl+nr)>>1;
	 if(mid>=l) change(p<<1,nl,mid,l,r,x);
	 if(mid<r) change(p<<1|1,mid+1,nr,l,r,x);
}
bool topo()
{
	 queue<int> q;
	 for(int i=1;i<=id;i++) if(!ind[i]) q.push(i);
	 for(int i=1;i<=id;i++) ds[i]=max(ds[i],1);
	 while(!q.empty())
	 {
	 	 int cur=q.front(); q.pop();
	 	 for(int i=hea[cur];i;i=nex[i])
		 {
		 	 ds[ver[i]]=max(ds[ver[i]],ds[cur]+edg[i]);
		 	 if(r[ver[i]] && r[ver[i]]>ds[ver[i]]) return false;
		 	 ind[ver[i]]--;
		 	 if(!ind[ver[i]]) q.push(ver[i]);
		 }
	 }
	 for(int i=1;i<=id;i++) if(ind[i] || ds[i]>1000000000) return false;
	 return true;
}
int main()
{
	 //ios::sync_with_stdio(false); cin.tie(0);
	 //freopen(".in","r",stdin);
	 //freopen(".out","w",stdout);
	 id=n=rd(),s=rd(),m=rd();
	 for(int i=1,p,d;i<=s;i++) p=rd(),d=rd(),r[p]=ds[p]=d;
	 build(1,1,n);
	 for(int i=1,l,r,k,Last;i<=m;i++)
	 {
	 	 Last=l=rd(),r=rd(),k=rd(),id++;
	 	 for(int j=1,pos;j<=k;j++)
	 	 {
	 	 	 pos=rd(),add_edge(id,pos,1);
	 	 	 if(pos>Last) change(1,1,n,Last,pos-1,id);
	 	 	 Last=pos+1;
		 }
		 if(Last<=r) change(1,1,n,Last,r,id);
	 }
	 if(topo()) { printf("TAK
"); for(int i=1;i<=n;i++) printf("%d ",ds[i]); printf("
"); }
	 else printf("NIE
");
	 //fclose(stdin);
	 //fclose(stdout);
	 return 0;
}

P5025 [SNOI2017]炸弹

在一条直线上有 (n) 个炸弹,每个炸弹的坐标是 (x_i),爆炸半径是 (r_i),当一个炸弹爆炸时,如果另一个炸弹所在位置 (x_j) 满足: (|x_j-x_i| le r_i),那么,该炸弹也会被引爆。

现在,请你帮忙计算一下,先把第 (i) 个炸弹引爆,将引爆多少个炸弹呢?

我们先进行普通的线段树优化建图,之后需要求出每个点能够到达的【叶子节点的个数】。

与其统计叶子节点,不如想方法记录下每个点最远能扩展到的最左端、最右端的炸弹编号。

容易想到用强联通分量解决问题。

我们对于这张图跑强联通分量,使图变为一张 DAG。

在 DAG 上我们可以暴力(相当于 dp)求出每个连通块能够到达的最远点,最终输出方案即可。

$ exttt{code}$
// Author:A weak man named EricQian
#include<bits/stdc++.h>
using namespace std;
#define infll 0x7f7f7f7f7f7f7f7f
#define inf 0x3f3f3f3f
#define Maxn 500005 // 空间!!!
#define pb push_back
#define mod 1000000007
typedef long long ll;
inline ll rd()
{
	 ll x=0;
	 char ch,t=0;
	 while(!isdigit(ch = getchar())) t|=ch=='-';
	 while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
	 return x=t?-x:x;
}
int n,id,tot,sum,Time,tp,ans;
ll x[Maxn],d[Maxn];
int hea[Maxn<<2],nex[Maxn<<4],ver[Maxn<<4];
int dfn[Maxn<<2],low[Maxn<<2],sta[Maxn<<2],bel[Maxn<<2];
bool ins[Maxn<<2],vis[Maxn<<2];
int tol[Maxn<<2],tor[Maxn<<2],num[Maxn<<2];
int L[Maxn<<2],R[Maxn<<2];
vector<int> g[Maxn<<2];
inline void add_edge(int x,int y){ ver[++tot]=y,nex[tot]=hea[x],hea[x]=tot; }
// 由于只用建立一棵树,所以将连出节点与连入节点合并。 
// 选择从上往下连边 
void build(int p,int nl,int nr)
{
	 if(nl==nr) { num[p]=nl; return; }
	 int mid=(nl+nr)>>1;
	 build(p<<1,nl,mid),build(p<<1|1,mid+1,nr);
	 num[p]=++id;
	 tol[id]=min(tol[num[p<<1]],tol[num[p<<1|1]]);
	 tor[id]=max(tor[num[p<<1]],tor[num[p<<1|1]]);
	 add_edge(num[p],num[p<<1]),add_edge(num[p],num[p<<1|1]);
}
void change(int p,int nl,int nr,int l,int r,int x)
{
	 if(nl>=l && nr<=r) { add_edge(x,num[p]); return; }
	 int mid=(nl+nr)>>1;
	 if(mid>=l) change(p<<1,nl,mid,l,r,x);
	 if(mid<r) change(p<<1|1,mid+1,nr,l,r,x);
}
void tarjan(int x)
{
	 dfn[x]=low[x]=++Time,sta[++tp]=x,ins[x]=true;
	 for(int i=hea[x];i;i=nex[i])
	 {
	 	 if(!dfn[ver[i]]) tarjan(ver[i]),low[x]=min(low[x],low[ver[i]]);
	 	 else if(ins[ver[i]]) low[x]=min(low[x],dfn[ver[i]]);
	 }
	 if(dfn[x]==low[x])
	 {
	 	 sum++,L[sum]=inf;
	 	 do
	 	 {
	 	 	 x=sta[tp--],ins[x]=false,bel[x]=sum,
	 	 	 L[sum]=min(L[sum],tol[x]),R[sum]=max(R[sum],tor[x]);
		 }while(dfn[x]!=low[x]);
	 }
}
void Find(int x)
{
	 vis[x]=true;
	 for(int v:g[x])
	 {
	 	 if(vis[v]) L[x]=min(L[x],L[v]),R[x]=max(R[x],R[v]);
	 	 else Find(v),L[x]=min(L[x],L[v]),R[x]=max(R[x],R[v]);
	 }
}
int main()
{
	 id=n=rd(),x[n+1]=infll;
	 for(int i=1;i<=n;i++) x[i]=rd(),d[i]=rd();
	 for(int i=1;i<=n;i++)
	 	 tol[i]=lower_bound(x+1,x+n+1,x[i]-d[i])-x,
	 	 tor[i]=upper_bound(x+1,x+n+1,x[i]+d[i])-x-1;
	 build(1,1,n);
	 for(int i=1;i<=n;i++) change(1,1,n,tol[i],tor[i],i);
	 for(int i=1;i<=id;i++) if(!dfn[i]) tarjan(i);
	 for(int i=1;i<=id;i++) for(int j=hea[i];j;j=nex[j])
	 	 if(bel[i]!=bel[ver[j]]) g[bel[i]].pb(bel[ver[j]]);
	 for(int i=1;i<=sum;i++) if(!vis[i]) Find(i);
	 for(int i=1;i<=n;i++)
	 	 ans=(ans+1ll*i*(R[bel[i]]-L[bel[i]]+1)%mod)%mod;
	 printf("%d
",ans);
	 return 0;
}

线段树分治(按时间分治)

P5787 二分图 /【模板】线段树分治

就是将每个操作放到线段树上,是的最终处理次数不超过 (nlog n) 次,从而优化复杂度。

这一题是常见的维护并查集操作,并不能使用路径压缩,而是启发式合并。

当我们进入一个时间段的时候,将这个点加入一个栈,并连边。

当我们要走回祖先时,再将栈中的东西弹出,使这个点的操作全部被撤回。

$ exttt{code}$
// Author:A weak man named EricQian
#include<bits/stdc++.h>
using namespace std;
#define infll 0x7f7f7f7f7f7f7f7f
#define inf 0x3f3f3f3f
#define Maxn 100005
#define pb push_back
#define pa pair<int,int>
#define fi first
#define se second
typedef long long ll;
inline int rd()
{
	 int x=0;
	 char ch,t=0;
	 while(!isdigit(ch = getchar())) t|=ch=='-';
	 while(isdigit(ch)) x=x*10+(ch^48),ch=getchar();
	 return x=t?-x:x;
}
inline ll maxll(ll x,ll y){ return x>y?x:y; }
inline ll minll(ll x,ll y){ return x<y?x:y; }
inline ll absll(ll x){ return x>0ll?x:-x; }
inline ll gcd(ll x,ll y){ return (y==0)?x:gcd(y,x%y); }
int n,m,k,tp;
int fa[Maxn<<1],siz[Maxn<<1];
pa sta[Maxn<<2];
vector<pa> g[Maxn<<2];
int Find(int x){ return (fa[x]==x)?x:Find(fa[x]); }
void Insert(int p,int nl,int nr,int l,int r,int x,int y)
{
	 if(nl>=l && nr<=r) { g[p].pb(pa(x,y)); return; }
	 int mid=(nl+nr)>>1;
	 if(mid>=l) Insert(p<<1,nl,mid,l,r,x,y);
	 if(mid<r) Insert(p<<1|1,mid+1,nr,l,r,x,y);
}
inline void merge(int x,int y)
{
//	 printf("merge(%d %d) ",x,y);
 	 x=Find(x),y=Find(y);
 	 if(x==y) return;
// 	 printf("fa : %d %d
",x,y);
	 if(siz[x]>siz[y]) swap(x,y);
 	 sta[++tp]=pa(x,y);
 	 fa[x]=y,siz[y]+=siz[x];
}
inline void del(int x,int y)
{
	 fa[x]=x,siz[y]-=siz[x];
}
void update(int p,int nl,int nr)
{
	 int Lasttop=tp,opt=1;
	 for(int i=0;i<g[p].size();i++)
	 {
	 	 if(Find(g[p][i].fi)==Find(g[p][i].se))
	 	 {
	 	 	 for(int j=nl;j<=nr;j++) printf("No
");
	 	 	 opt=0;
	 	 	 break;
		 }
		 merge(g[p][i].fi,g[p][i].se+n);
		 merge(g[p][i].fi+n,g[p][i].se);
	 }
	 if(opt)
	 {
		 if(nl==nr) printf("Yes
");
		 else
		 {
		 	 int mid=(nl+nr)>>1;
		 	 update(p<<1,nl,mid),update(p<<1|1,mid+1,nr);
		 }
	 }
	 while(tp>Lasttop) del(sta[tp].fi,sta[tp].se),tp--;
}
int main()
{
	 //ios::sync_with_stdio(false); cin.tie(0);
	 freopen("9.in","r",stdin);
	 freopen("my.out","w",stdout);
	 n=rd(),m=rd(),k=rd();
	 for(int i=1;i<=n+n;i++) fa[i]=i,siz[i]=1;
	 for(int i=1,x,y,l,r;i<=m;i++)
	 {
	 	 x=rd(),y=rd(),l=rd(),r=rd();
		 if(l<r) Insert(1,1,k,l+1,r,x,y);
	 }
	 update(1,1,k);
	 //fclose(stdin);
	 //fclose(stdout);
	 return 0;
}

CF1140F Extending Set of Points

题意:给定一个直角坐标系,有若干个点 ((x_i,y_i)) 会在 ([l_i,r_i]) 时间内出现。定义一个图的张集为 (S),若干 ((x_1,y_1),(x_1,y_2),(x_2,y_1)) 都在这个集合中,那么 ((x_2,y_2)) 也在这个集合中。对于每个时刻,求出张集大小。

只要将行和列拆开考虑,其实操作是一样一样的。

发现如果将一个点 ((x,y)) 看做有 (x ightarrow (y+n)) 的一条无向边,显然会形成一张二分图。

那么最终产生的张集一定是每个连通的二分图中,一边的点数乘上另一边的点数,因此像上一题一样维护并查集就可以了。

P5227 [AHOI2013]连通图

将询问离线下来,接下来就是模板了。

然而我学艺不精,不会线段树分治更广泛的使用方法,如 P4585 [FJOI2015]火星商店问题(和可持久化 Trie 结合)或 P3767 膜法,有空再补吧。。。

原文地址:https://www.cnblogs.com/EricQian/p/15500705.html