混合果汁:整体二分,线段树

$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 }
View Code

然而我写整体二分纯属为了练手。。。

主席树直接弄不就好了?还少个$log$

然而我又不是刚学主席树。。。

 

posted @ 2020-02-13 19:34  DeepinC  阅读(248)  评论(0编辑  收藏  举报