Loading

线段树练习

Ⅰ.差分与前缀和

P2184 贪婪大陆

题意 :

给定防线长度 \(n\) 和操作次数 \(m\), 每次在 [\(l\) ,\(r\)] 内布下一种雷,查询区间雷的种类数。

分析 :

用线段的方式表示区间布的雷 :


如[ 2 , 4 ]内的种类 = [ 1 , 4 ]内的起点 - [ 1 , 2 ]内的终点

P1438 无聊的数列

题意 :

区间加等差数列,区间查询。

分析 :

将原序列差分,即可转化为区间加。

\(Summary\)

在[ \(l\) ,\(r\) ] 内加上一个首项为 \(k\) ,公差为 \(D\) 的等差数列,等价于在差分数组上的 \(l\)\(k\) , 然后在在 [ \(l+1\) , \(r\)] 加上\(D\), 最后在 \(r+1\) 减去 \([ k+D*(r-l) ]\)

Ⅱ. 贪心

P1607 [ USACO09FEB ] Fair Shuttle G

题意 :

\(1\)\(n\) 在有 \(k\) 组cow,想从 \(s\)\(e\), bus只能单向行驶,且容量为c, 问最大能有多少cow能达成目的。 (其中每组cow可以只有部分上车)

分析 :

用线段树模拟上车的过程,显然应该按照右端点排序。

然后就可以边模拟边用线段树维护已经在车上的最大人数。

P1937 [ USACO10MAR ] Barn Allocation G

题意 :

\(n\) 个点,每个点有一个容量 \(c_i\), \(m\) 次操作,在\([l , r]\) 内加上 \(1\),问最多能加几次。

分析 :

做完上一道就很点了,维护区间minn,可以就 \(-1\)\(ans\)++

Ⅳ. 最大子段和

单独拿出来了,也算是一类吧。目前做过的都比较明显。

P4513 小白逛公园 & GSS3 - Can you answer these queries III

题意 :

  1. 查询区间最大子段和; 2.单点修改

分析 :

板子。

将子树 \(ls\)\(rs\) 的节点信息上传到父节点 \(u\) 时,对于 \(u\) 维护的序列中,和最大的子段有两种情况:

\(case1:\) 子段不经过中点,那么 \(u\) 的答案为 \(ls\)\(rs\) 的答案的最大值。
\(case2:\) 子段经过了中点。这种情况比较复杂,因为我们无法知道子树的答案所对应的序列。这也是本题的难点所在。
接下来对第 22 种情况进行重点分析:

我们记 \(ms\) 为区间最长子段和,\(sum\) 为区间和,\(prel\)\(prer\) 分别表示从区间左端点和右端点开始的最大子段和。

考虑 $ pushup \(:\)sum$ 可以直接上传,$prel[u]=\max(prel[ls],sum[ls]+prel[rs]) \quad \((\)prer$ 同理)
$ms[u]=\max(res[ls],ms[rs],prer[ls]+prel[rs]) \quad $

\(Notice\)

\(query\)一点变化

  
Info query(int u,int l,int r){
	if(tr[u].l>=l and tr[u].r<=r){
		return tr[u];
	}
	if(r<=mid) return query(ls,l,r);  //千万注意
	if(l>mid) return query(rs,l,r);
	Info T, L=query(ls,l,r), R=query(rs,l,r); 
	T.sum=L.sum+R.sum;
	T.lsum=max(L.lsum,L.sum+R.lsum);
	T.rsum=max(R.rsum,R.sum+L.rsum);
	T.ms=max(max(L.ms,R.ms),L.rsum+R.lsum);
	return T;
}

参考:SiyuanSP1716 sol

P2572 [SCOI2010] 序列操作

题意 :

  • 0 l r\([l, r]\) 区间内的所有数全变成 \(0\)
  • 1 l r\([l, r]\) 区间内的所有数全变成 \(1\)
  • 2 l r\([l,r]\) 区间内的所有数全部取反,也就是说把所有的 \(0\) 变成 \(1\),把所有的 \(1\) 变成 \(0\)
  • 3 l r 询问 \([l, r]\) 区间内总共有多少个 \(1\)
  • 4 l r 询问 \([l, r]\) 区间内最多有多少个连续的 \(1\)

分析 :

类似最大子段和的思想,相当于记录了2个最大子段和(0和1),同时维护前后缀\(sum\)

$Notice : $ 前后缀 \(sum\) 的更新
\(\quad\quad\quad u.prel_0= ls.sum_o ~?~ ls.prel_0 : ls.sum_0 + rs.prel_o\)

P4118 [Ynoi2018] 末日时在做什么?有没有空?可以来拯救吗?

不会

Ⅴ. 优化建图

\(Statement\)

大多与最短路有关(其它的太难了也不去做) 建一棵in树和out树,in树由上到下连边,再连向out树,out树自下而上连边。这样,如果要连 $ 1$ 到 \([ 4 ,6 ]\) 的边,只需于fa节点相连。

\(O(n) -\)> $O( \log_ 2x ) $

参考:maoyiting

题意 :

实际上不需要开structure,只需要记录左右 \(l,r\),以及一个极大的 \(\Delta K\) ,为了分别表示 \(in\) 树和 \(out\) 树,然后直接建图即可。

CF786B Legacy

题意 : 1. 把\(u,v\)\(w\) 的边  2.把 \(u\) 连向 \([l ,r]\) 3.把 \([l , r]\)连向u. 求s号点到其它的最短路。

\(sol\):

线段树优化建图,\(dijkstra\).

\(code\) :


void add(){

}

void build(int u,int l,int r){
	if(l==r){
		a[l]=u; return;
	}
	add(u,ls,0); add(u,rs,0);
	add(ls+k,u+k,0); add(rs+k,u+k,0);
	build(ls,l,mid); build(rs,mid + 1,r);
}

void modify(int u,int l,int r,int x,int y,int v,int w){
	if(l>=x and r<=y){
		if(opt==2) add(v+k,u,w);
		else add(u+k,v,w);
		return;
	}
	if(x<=mid) modify(ls,l,mid,x,y,v,w);
	if(y>mid) modify(rs,mid+1,r,x,y,v,w);
}

void djkstra(){

}

signed main(){
	read(n,m,s);
	build(1,1,n);
	rep(i,1,n) add(a[i],a[i]+k,0),add(a[i]+k,a[i],0);
	rep(i,1,m){
		read(opt);
		if(opt==1){
			int u,v,w; read(u,v,w);
			add(a[u]+k,a[v],w);
		}
		else{
			int u,l,r,w; read(u,l,r,w);
			modify(1,1,n,l,r,a[u],w);
		}
	}
	dijkstra(a[s]+k);
	rep(i,1,n){
		printf("%lld ",dis[a[i]]==0x3f3f3f3f3f3f3f3fll? -1:dis[a[i]]);
	}
	return 0;
}


Ⅵ. 二分

权值线段树上可以直接二分,应用:逆序对

P2824 [HEOI2016/TJOI2016] 排序

题意 :

一个排列n,m次操作,把\([l,r]\)升序或降序,求最后完成操作后第p位置上的数。

分析 :

  1. 暴力排序 + 超快读,喜提80分
  2. 正解 : 线段树 + 二分。 二分第p位置上的数,把小于p的数改为0,大于等于p的数改为1,。记\([l,r]\)内1的个数为 $ cnt $ ,如果把\([l,r]\)升序,就把 \(r-cnt+1\) 的数都改为\(1\),其余改为\(0\);降序相反。第一个是\(1\)的位置就是答案。

\(code\) :


void pushup(int u){
	tr[u].sum=tr[ls].sum+tr[rs].sum;
}

void pushdown(int u){
	if(~tr[u].tag){
		tr[ls].tag=tr[rs].tag=tr[u].tag;
		tr[ls].sum=tr[u].tag*len(tr[ls]);
		tr[rs].sum=tr[u].tag*len(tr[rs]);
		tr[u].tag=-1;
	}
}

void build(int u,int l,int r,int k){
	tr[u]={l,r,0,-1};
	if(l==r){
		tr[u].sum=(a[l]>=k);
		return;
	}
	build(ls,l,mid,k); build(rs,mid+1,r,k);
	pushup(u);
}

void assign(int u,int l,int r,int k){
	if(tr[u].l>=l and tr[u].r<=r){
		tr[u].sum=len(tr[u])*k;
		tr[u].tag=k;
		return;
	}
	pushdown(u);
	if(l<=mid) assign(ls,l,r,k);
	if(r>mid) assign(rs,l,r,k);
	pushup(u);
}

int query(int u,int l,int r){
	return sum;
}

int check(int k){
	build(1,1,n,k);
	rep(i,1,m){
		int l=x[i],r=y[i],k=op[i];
		int cnt=query(1,l,r);
		if(!k){
			assign(1,r-cnt+1,r,1);
			assign(1,l,r-cnt,0);
		}		
		else{
			assign(1,l,l+cnt-1,1);
			assign(1,l+cnt,r,0);
		}
	}
	return query(1,p,p);
}

int main(){
	......
	int l=0,r=n;
	while(l<r){
		int MID=(l+r+1)>>1;
		if(check(MID)) l=MID;
		else r=MID-1;
	}
	cout<<l;
	return 0;
}

P4344 [SHOI2015] 脑洞治疗仪 :

题意 :

一个长度为\(n\)\(01\)序列,初始时均为\(1\),三种操作:

  1. \([l,r]\)覆盖为0。
  2. \([l_o,r_o]\)复制到\([l_1,r_1]\),并把\([l_0,r_0]\)覆盖为\(0\)。如果\(len_1>len_0\),那么就从\(l_1\)开始,如果\(len_1<len_0\),则直接把\(len_0\)多出的部分直接去掉。
  3. 查询\([l,r]\)内最长连续的\(0\)的长度。

分析 :

  1. 查询最长连续子段和,考虑记录 \(0\)\(sum,lsum,rsum,ms\), \(Pushup\)\(Pushdown\) 同序列操作。
  2. 考虑操作\(2\),只需考虑\([l1,r1]\)会复制到的右端点,很显然的是,\(0\)的个数具有单调性,\(r_1\)越大,\(0\)的个数越多,可以二分右端点,\(check(0)\)的个数。

void work(){
	int l0,r0,l1,r1;
	scanf("%d%d%d%d",&l0,&r0,&l1,&r1);
	int cnt=find(1,l0,r0,1);
	if(cnt==0) return;
	modify(1,l0,r0,0);
	int l=l1,r=r1;
	while(l<r){
		int M=(l+r)>>1;
		if(find(1,l1,M,0)<cnt) l=M+1;
		else r=M;
	}
	modify(1,l1,l,1);
}

Ⅶ.双指针

P1712 [NOI2016] 区间

题意 :

在数轴上有 \(n\) 个闭区间从 \(1\)\(n\) 编号,第 \(i\) 个闭区间为 \([l_i,r_i]\)

现在要从中选出 \(m\) 个区间,使得这 \(m\) 个区间共同包含至少一个位置,即使得存在一个 \(x\) ,使得对于每一个被选中的区间 \([l_i,r_i]\),都有 \(l_i \leq x \leq r_i\)

对于一个合法的选取方案,它的花费为被选中的最长区间长度减去被选中的最短区间长度。

区间 \([l_i,r_i]\) 的长度定义为 \((r_i-l_i)\) ,即等于它的右端点的值减去左端点的值。

求所有合法方案中最小的花费。如果不存在合法的方案,输出 \(-1\)

分析 :

先求出所有区间的 \(len\) ,然后从小到大排序,顺次加入区间,当满足要求时,再从头减掉区间,使得恰好 \({\exists} x\) 被覆盖了 \(m\) 次,此时 \(ans=len_j-len_i\) 即为最小值。

考虑如何统计每个点的覆盖次数,显然可以线段树维护区间 \(\max\) , 只要区间 \(\max = m\) 即可。

\(Notice\) :

此题在 \(build\) 函数中,需要 \(n\)<<\(1\),线段树需要开 \(4*2=8\) 倍空间(因为区间有两个端点,要乘 \(2\) )。

\(code\) :


int n,m;
struct pos{
	int l,r,len;
}p[N<<4];
struct Info{
	int l,r,maxn,tag;
}tr[N<<3];
vector<int> v;

void pushup(int u){
}

void pushdown(int u){

}

void build(int u,int l,int r){
	
}

void modify(int u,int l,int r,int k){
	
}

signed main(){
	scanf("%lld%lld",&n,&m);
	build(1,1,n<<1);
	rep(i,1,n){
		int l,r; scanf("%lld%lld",&l,&r);
		p[i]={l,r,r-l};
		v.emplace_back(l);
		v.emplace_back(r);
	}
	sort(p+1,p+1+n,[&](pos a,pos b){return a.len<b.len;});
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	auto find=[&](int x){
		return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
	};
	rep(i,1,n){
		p[i].l=find(p[i].l);
		p[i].r=find(p[i].r);
	}
	int ans=1e9,i=0,j=0;
	while(j<n){
		while(tr[1].maxn<m and j<n){
			j++;
			modify(1,p[j].l,p[j].r,1);
		}
		if(tr[1].maxn<m) break;
		while(tr[1].maxn>=m and i<j){
			i++;
			modify(1,p[i].l,p[i].r,-1);
		}
		ans=min(ans,p[j].len-p[i].len);
	}
	printf("%lld\n",ans==1e9?-1:ans);
	return 0;
}
posted @ 2024-01-13 18:53  Liuyc07  阅读(17)  评论(0编辑  收藏  举报