混合果汁:整体二分,线段树
$Description:$
小 R 热衷于做黑暗料理,尤其是混合果汁。
商店里有n种果汁,编号为1~n。i号果汁的美味度是$d_i$,每升价格为$p_i$.小 R 在制作混合果汁时,还有一些特殊的规定,即在一瓶混合果汁中,i号果汁最多只能添加$l_i$升。
现在有m个小朋友过来找小R要混合果汁喝,他们都希望小 R 用商店里的果汁制作成一瓶混合果汁。
其中,第m个小朋友希望他得到的混合果汁总价格不大于$g_j$,体积不小于$L_j$。
在上述这些限制条件下,小朋友们还希望混合果汁的美味度尽可能地高,一瓶混合果汁的美味度等于所有参与混合的果汁的美味度的最小值。请你计算每个小朋友能喝到的最美味的混合果汁的美味度。
$n,m,p_i,l_i,d_i \le 100000,g_j,L_j \le 10^12$
今天刚学整体二分,做道题试试。
整体二分其实和正常的二分差不多,只不过是离线下来一起二分了而已,所以叫整体二分。
往往应用与单次$check$复杂度过高的时候。
通常形式也就是$solve(l,r,L,R)$分别表示答案域以及询问区间。
然后进行数据结构的修改等,调整至$mid$的状态。然后所有询问利用数据结构看答案是否大于$mid$。
如果大于$mid$就放进右区间否则放进左区间。递归解决。到了叶节点也就是答案了。
这道题其实挺模板的。如果知道是整体二分的话。
直接把每种果汁按照美味度排序,然后二分的答案是下标即可。
二分的$check$就是看用这个小朋友的钱买任意美味度大于$mid$的果汁,看能否买到足量的果汁。
在二分之后,果汁之间就没有美味度的区别了,只要大于等于$mid$就行。我们在意的只是价格与数量。
这样的话我们肯定先买便宜的。要维护支持加入,删除,查询价格排序后的前缀和。
以价格为下标建一个权值线段树就行,维护饮料总量和总价格。
如果能买下当前区间所有的就买下来。否则如果买不起左儿子就递归进左儿子,买得起左儿子就递归进右儿子。叶节点特殊处理一下就行了。
在$solve(l,r,L,R)$时只要把$[mid+1,r]$的果汁加入线段树,$check$所有询问,然后解决左儿子,然后再把这些果汁从线段树里删除,再解决右儿子。
这样,线段树的总修改次数是区间长度和即$O(n \ log \ n)$的。询问次数是问题数乘分治层数也就是$O(q \ log \ n)$的。
总复杂度$O((n+q)log^2 \ n)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define S 100005 5 #define lc p<<1 6 #define rc p<<1|1 7 #define mid (l+r>>1) 8 int ans[S],n,q;ll totp[S<<2],totl[S<<2]; 9 struct ps{int p,v;ll l;friend bool operator<(ps a,ps b){return a.v<b.v;}}P[S]; 10 struct qs{ll m,w;int o;}Q[S],rq[S]; 11 void add(int pr,ll L,int p=1,int l=1,int r=100000){ 12 totl[p]+=L;totp[p]+=L*pr; 13 if(l==r)return; 14 if(pr>mid)add(pr,L,rc,mid+1,r); 15 else add(pr,L,lc,l,mid); 16 } 17 ll ask(ll m,int p=1,int l=1,int r=100000){ 18 if(m>=totp[p])return totl[p]; 19 if(l==r)return m/l; 20 if(m>=totp[lc])return totl[lc]+ask(m-totp[lc],rc,mid+1,r); 21 return ask(m,lc,l,mid); 22 } 23 void solve(int l,int r,int ql,int qr){ 24 if(l==r){while(qr>=ql)ans[Q[qr].o]=P[l].v,qr--;return;} 25 int pl=ql,pr=qr,md=l+r>>1; 26 for(int i=md+1;i<=r;++i)add(P[i].p,P[i].l); 27 for(int i=ql;i<=qr;++i)if(ask(Q[i].m)>=Q[i].w)rq[pr--]=Q[i];else rq[pl++]=Q[i]; 28 for(int i=ql;i<=qr;++i)Q[i]=rq[i]; 29 solve(l,md,ql,pl-1); 30 for(int i=md+1;i<=r;++i)add(P[i].p,-P[i].l); 31 solve(md+1,r,pl,qr); 32 } 33 int main(){ 34 cin>>n>>q;P[0].v=-1; 35 for(int i=1;i<=n;++i)scanf("%d%d%lld",&P[i].v,&P[i].p,&P[i].l); 36 for(int i=1;i<=q;++i)scanf("%lld%lld",&Q[i].m,&Q[i].w),Q[i].o=i; 37 sort(P+1,P+n+1); 38 solve(0,n,1,q); 39 for(int i=1;i<=q;++i)printf("%d\n",ans[i]); 40 }
然而我写整体二分纯属为了练手。。。
主席树直接弄不就好了?还少个$log$
然而我又不是刚学主席树。。。