[题解]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;
}
浙公网安备 33010602011771号