题解,完全不配黑
2025-12-28 21:01:18
发布于:广东
这题很简单,普通的主席树上二分,除了思维有点难度就是版,紫题里也是下位哪来的黑
本贴将会讲解两种解法。
没错,主席树
前排提醒:这只是主席树的一种做法
步骤:
1.一眼二分法。比答案小的所有美味度都能符合要求,比答案大的美味度不能,符合单调性。
2.考虑进行一次询问。每次 check 应尽量取美味度大于 、单价最小的果汁,取完体积再判断价格是否符合标准。可以建立值域线段树,以单价为下标,维护区间体积和与区间花费和(将区间所有果汁买下的花费),在值域线段树上进行线段树二分,可用体积小于等于左子树则向左递归,大于则更新可用体积并向右递归,最后返回消费和。如何维护美味值大于等于 的约束条件?注意到每次只有美味值在区间 的元素可用,形成一个后缀关系。将美味值从大到小排列,将后缀关系转化为前缀关系,只考虑前缀的所有元素,明显可用主席树维护。具体编码时先从大到小排序美味度,然后建立主席树,二分某种果汁之前的所有果汁是否可用(而不用对美味度值域建树),再按上述步骤二分。注意到每次 check 复杂度为 ,一次二分答案为 .推广到多次询问,可以用整体二分直接在线对每一个询问回答即可。总复杂度 。
哪有人两步推出解法的
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct node{
int d,p,l;
bool operator <(const node&xyl)const{return d>xyl.d;}
}a[100005];
int n,q,cnt,root[100005],g,L;
struct pst{
int l,r,cost,vol;
}t[100005<<5];
void add(int &cur,int lst,int l,int r,int p,int v){
cur=++cnt;
t[cur]=t[lst];
t[cur].cost+=p*v,t[cur].vol+=v;
if(l==p&&r==p)return;
int mid=l+r>>1;
if(p<=mid)add(t[cur].l,t[lst].l,l,mid,p,v);
else add(t[cur].r,t[lst].r,mid+1,r,p,v);
}
int query(int cur,int l,int r,int p){
if(l==r)return l*p;
if(t[t[cur].l].vol>=p)return query(t[cur].l,l,l+r>>1,p);
else return t[t[cur].l].cost+query(t[cur].r,(l+r>>1)+1,r,p-t[t[cur].l].vol);
}
int read(){
int x=0,f=1,ch=getchar_unlocked();
for(;!isdigit(ch);ch=getchar_unlocked())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar_unlocked())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
void write(int x){
if(x<0)putchar_unlocked('-'),x=-x;
if(x>=10)write(x/10);
putchar_unlocked(x%10+'0');
}
signed main(){
n=read(),q=read();
for(int i=1;i<=n;++i)a[i]={read(),read(),read()};
sort(a+1,a+n+1);
for(int i=1;i<=n;++i)add(root[i],root[i-1],1,100005,a[i].p,a[i].l);
while(q--){
g=read(),L=read();
int l=1,r=n,ans=-1,mid,k;
while(l<=r){
mid=l+r>>1,k=query(root[mid],1,100005,L);
if(k<=g&&t[root[mid]].vol>=L)ans=mid,r=mid-1;
else l=mid+1;
}
write(~ans?a[ans].d:ans),putchar('\n');
}
return 0;
}
没错,整体二分
注意你谷有很多复杂度假的整体二分,也就是 甚至 ,请以我学习的这种思路和代码为参考,或者自己证明自己做法的复杂度。
貌似可以一次二分中再二分符合要求的价格,但是这样就会是二分套二分套整体二分,复杂度为 .
1.与上述一样。
2.用整体二分套值域线段树。利用值域线段树二分,判断询问分到哪组。区别是不像主席树开一瞬千树一堆线段树,而是一次性使用,用的时候插元素进去,用完清空。但是如果每次都从 的果汁插到 的果汁复杂度会假,因此每次都复用原来的权值线段树 ,往左就插入更多元素,往右就删掉一些元素。如何证明复杂度?
注意到整体二分使用了递归,本质上是从值域的左到右执行操作,而不是同时进行,可以依此证明。
插入的用时分为从该轮递归跳到同一层递归和从该轮递归跳到更高层的递归。每层插入复杂度为左右层相加再加上左层最底部跳到右层最顶部的复杂度。计算得如下式:
计算次数为 ,每次插入 ,故总插入复杂度为 ,高于一般整体二分的 ,但是加上询问为 ,依旧达标。使用小技巧优化只能优化少数极端情况或递归开销。
整体二分可能比主席树更难理解,但是……
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=100005;
int n,m,vol[N<<2],cost[N<<2],cur,ans[N];
struct node{
int d,p,l;
bool operator <(const node&x){return d>x.d;}
}a[N];
struct xyl{
int g,l,id;
}q[N],q1[N],q2[N];
void pushup(int p){vol[p]=vol[p<<1]+vol[p<<1|1],cost[p]=cost[p<<1]+cost[p<<1|1];}
void add(int p,int l,int r,int P,int L){
if(l==r){
vol[p]+=L;
cost[p]=vol[p]*l;
return;
}
int mid=l+r>>1;
if(P<=mid)add(p<<1,l,mid,P,L);
else add(p<<1|1,mid+1,r,P,L);
pushup(p);
}
int query(int p,int l,int r,int v){
if(!v)return 0;
if(l==r)return l*v;
int mid=l+r>>1;
if(vol[p<<1]>=v)return query(p<<1,l,mid,v);
else return cost[p<<1]+query(p<<1|1,mid+1,r,v-vol[p<<1]);
}
void solve(int l,int r,int ql,int qr){
if(ql>qr)return;
if(l==r){
for(int i=ql;i<=qr;++i)ans[q[i].id]=a[l].d;
return;
}
int mid=l+r>>1,cnt1=0,cnt2=0;bool b1=0,b2=0;
while(cur<mid)++cur,add(1,1,N,a[cur].p,a[cur].l);
while(cur>mid)add(1,1,N,a[cur].p,-a[cur].l),--cur;
for(int i=ql;i<=qr;++i){
if(vol[1]>=q[i].l&&query(1,1,N,q[i].l)<=q[i].g)q1[++cnt1]=q[i],b1=1;
else q2[++cnt2]=q[i],b2=1;
}
for(int i=1;i<=cnt1;++i)q[ql+i-1]=q1[i];
for(int i=1;i<=cnt2;++i)q[ql+cnt1+i-1]=q2[i];
if(b1)solve(l,mid,ql,ql+cnt1-1);
if(b2)solve(mid+1,r,ql+cnt1,qr);
}
int read(){
int x=0,f=1,ch=getchar_unlocked();
for(;!isdigit(ch);ch=getchar_unlocked())if(ch=='-')f=-1;
for(;isdigit(ch);ch=getchar_unlocked())x=(x<<3)+(x<<1)+(ch^48);
return x*f;
}
void write(int x){
if(x<0)putchar_unlocked('-'),x=-x;
if(x>=10)write(x/10);
putchar_unlocked(x%10+'0');
}
signed main(){
n=read(),m=read();
for(int i=1;i<=n;++i)a[i]={read(),read(),read()};
a[++n]={-1,0,N};
sort(a+1,a+n+1);
for(int i=1;i<=m;++i)q[i]={read(),read(),i};
solve(1,n,1,m);
for(int i=1;i<=m;++i)write(ans[i]),putchar('\n');
return 0;
}
时间复杂度:
本题其它解法:树状数组套树状数组
这里空空如也







有帮助,赞一个