[题解]P1250 种树

P1250 种树

这里主要补充一个\(O(h\log n)\)的做法(即加强版 P11453 [USACO24DEC] Deforestation S)。

我们将需求看做线段,第\(i\)条线段左右端点分别是\(l_i,r_i\),要种\(cnt_i\)棵树。

那么可以使用贪心的思想求解。将线段按左端点从大到小排序,遍历每条线段\(i\)

  • 如果线段\(i\)中树的棵数\(s_i\ge cnt_i\),那么跳过即可。
  • 如果线段\(i\)中树的棵树\(s_i<cnt_i\),那么我们需要从\(l_i\)开始,找到空位就种,直到\(s_i=cnt_i\)为止。

如果朴素遍历,这一步骤的时间复杂度是\(O(hn)\)

但是,在第二种情况下,如果我们可以快速找出一个位置\(k\),使得\([l_i,k]\)中恰好有\(cnt_i-s_i\)个空位,那么我们就可以直接利用线段树的区间赋值一次性把这些树种好(种好为\(1\),没种好为\(0\))。

为了计算\(k\),我们先考虑如何计算从下标\(1\)开始的第\(k\)个空位出现的位置。

可以使用线段树+二分,单次时间复杂度为\(O(\log^2 n)\)

如果我们把二分挪到线段树上(线段树上二分),单次时间复杂度将降至\(O(\log n)\),代码如下:

int kth0(int x,int k,int l,int r){//查询从下标l开始,第k个0的位置
	if(l==r) return l;
	pushdown(x,l,r);
	int mid=(l+r)>>1,lef0=mid-l+1-sum[lc];//lef0表示左子树中0的个数
	if(lef0>=k) return kth0(lc,k,l,mid);
	else return kth0(rc,k-lef0,mid+1,r);
}

总复杂度 \(O(h\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define N 30010
#define H 5010
using namespace std;
int n,h,ans;
struct SEG{
	#define lc (x<<1)
	#define rc (x<<1|1)
	int sum[N<<2],tag[N<<2];
	void pushup(int x){sum[x]=sum[lc]+sum[rc];}
	void pushdown(int x,int l,int r){
		if(!tag[x]) return;
		int mid=(l+r)>>1;
		sum[lc]=(mid-l+1)*tag[x],sum[rc]=(r-mid)*tag[x];
		tag[lc]=tag[rc]=tag[x],tag[x]=0;
	}
	int kth0(int x,int k,int l,int r){
		if(l==r) return l;
		pushdown(x,l,r);
		int mid=(l+r)>>1,lef0=mid-l+1-sum[lc];
		if(lef0>=k) return kth0(lc,k,l,mid);
		else return kth0(rc,k-lef0,mid+1,r);
	}
	void ser(int x,int a,int b,int v,int l,int r){
		if(a<=l&&r<=b) return tag[x]=v,sum[x]=(r-l+1)*v,void();
		pushdown(x,l,r);
		int mid=(l+r)>>1;
		if(a<=mid) ser(lc,a,b,v,l,mid);
		if(b>mid) ser(rc,a,b,v,mid+1,r);
		pushup(x);
	}
	int query(int x,int a,int b,int l,int r){
		if(a>b) return 0;
		if(a<=l&&r<=b) return sum[x];
		pushdown(x,l,r);
		int mid=(l+r)>>1,ans=0;
		if(a<=mid) ans+=query(lc,a,b,l,mid);
		if(b>mid) ans+=query(rc,a,b,mid+1,r);
		return ans;
	}
}tr;
struct Seg{int l,r,cnt;}a[H];
signed main(){
	cin>>n>>h;
	for(int i=1;i<=h;i++) cin>>a[i].l>>a[i].r>>a[i].cnt;
	sort(a+1,a+1+h,[](Seg a,Seg b){return a.l>b.l;});
	for(int i=1;i<=h;i++){
		int sum=tr.query(1,a[i].l,a[i].r,1,n);
		if(sum<a[i].cnt){
			ans+=a[i].cnt-sum;
			int pos=tr.kth0(1,a[i].cnt-sum+a[i].l-1,1,n);
			tr.ser(1,a[i].l,pos,1,1,n);
		}
	}
	cout<<ans<<"\n";
	return 0;
}

upd on 2025/10/29:从 __Deity_Ling__ 大佬处学到了更优雅的实现。用 \(nxt[i]\) 维护 \(\ge i\) 的最大空位。这样就不需要区修,也不需要线段树上二分了,你甚至可以直接使用树状数组。时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define N 30010
#define H 5010
using namespace std;
int n,h,ans,nxt[N];
struct SEG{
	#define lc (x<<1)
	#define rc (x<<1|1)
	int sum[N<<2],tag[N<<2];
	void pushup(int x){sum[x]=sum[lc]+sum[rc];}
	void sep(int x,int a,int v,int l,int r){//单点加和单点赋值是一样的
		if(l==r) return sum[x]=v,void();
		int mid=(l+r)>>1;
		if(a<=mid) sep(lc,a,v,l,mid);
		else sep(rc,a,v,mid+1,r);
		pushup(x);
	}
	int query(int x,int a,int b,int l,int r){
		if(a<=l&&r<=b) return sum[x];
		int mid=(l+r)>>1,ans=0;
		if(a<=mid) ans+=query(lc,a,b,l,mid);
		if(b>mid) ans+=query(rc,a,b,mid+1,r);
		return ans;
	}
}tr;
struct Seg{int l,r,cnt;}a[H];
signed main(){
	cin>>n>>h;
	for(int i=1;i<=n;i++) nxt[i]=i;
	for(int i=1;i<=h;i++) cin>>a[i].l>>a[i].r>>a[i].cnt;
	sort(a+1,a+1+h,[](Seg a,Seg b){return a.l>b.l;});
	for(int i=1;i<=h;i++){
		int s=tr.query(1,a[i].l,a[i].r,1,n),p=a[i].l;
		while(s<a[i].cnt){
			s++,ans++;
			tr.sep(1,nxt[p],1,1,n);
			nxt[p]=nxt[nxt[p]+1];
		}
	}
	cout<<ans<<"\n";
	return 0;
}
posted @ 2025-05-28 16:06  Sinktank  阅读(46)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.