[CTSC2018] 混合果汁
先考虑如何处理单个询问,看到了最大值最小,不难想到二分
普通二分怎么做呢?设当前二分值为\(mid\),就不用考虑美味度小于\(mid\)的果汁了,于是就变成了一个简单的贪心问题,只需要将果汁按照价格排序,然后依次选择(注意选择的果汁不能超过\(l_i\)),最后选取了\(L_j\)升之后,查看总价格是否大于\(g_j\)即可
那么怎么将普通二分转换成整体二分呢?对于整体二分,肯定是先设solve(int lval,int rval,int st,int ed)
表示对于属于\(st\) ~ \(ed\)的小朋友,他们的答案在\(lval\)和\(rval\)之间。同样不用考虑美味度小于\(mid\)的果汁,大于等于\(mid\)的果汁全部都要考虑。对每一个小朋友,都执行普通二分的算法。但是我们现在不可能在递归树的每一个节点都进行一次按照价格排序,为了避免时间复杂度退化,我们就利用桶排序,以价格为下标建立树状数组,将美味度不低于\(mid\)的果汁扔进树状数组里面(注意有两个树状数组,下标都是价格,一个用来记录体积,另一个用来记录体积乘以价格;后者是用来判断是否符合条件的),然后再二分价格(其实树状数组加二分可以省一个\(\log\)),用第一个树状数组找到能买到当前所需要的体积的果汁的最小价格(这个最小价格指的是:如果买的果汁的价格低于这个价格,那么就算把低于这个价格的果汁全买了,得到的体积也比所需要的体积小,而把不超过这个价格的所有果汁都买了就能得到不低于所需要体积的果汁),然后再利用另一个树状数组判断买下这些果汁是否超过已有钱数
整体二分的话,这里就不能像之前那样子将果汁分成两部分了,因为每次check都需要用到整个果汁序列的,只能将小朋友分成两个部分;然而为了减少时间复杂度,我们假设在刚进入函数solve(int lval,int rval,int st,int ed)
(表示对于属于\(st\) ~ \(ed\)的小朋友,他们的答案在\(lval\)和\(rval\)之间)时,树状数组存储了大于\(rval\)的果汁信息,于是我们就要先将\([mid+1,rval]\)的果汁信息扔进树状数组里面,而且在进入solve(mid+1,rval,st+lt,ed)
之前要还原树状数组(注意进入solve(lval,mid,st,st+lt-1)
不用还原)
具体可以见代码
这道题目就告诉我们不一定非要将序列分成两部分,有可能是会用到整个序列的,这个时候在必要时还原就好了
update 2024.9.23
重新做一遍,做出来了
其实关键就是在二分的时候不能将果汁分成两部分,但我们也没有办法在搜索树的每一个节点都存储整个序列(这样复杂度会爆炸),所以我们只能假设在进入某一个节点的时候有一个性质,在回溯的时候又有一个性质。这里的话我们就假设在进入一个节点solve(int lval,int rval,int st,int ed)
的时候,我们已经存储了大于\(rval\)的果汁信息,这样子我们只需要先将\([mid+1,rval]\)的果汁信息扔进树状数组里面就可以直接进行计算,将这个节点的小朋友分成两组;而为了维持性质,我们进入节点solve(lval,mid,st,st+lt-1)
之前就不要还原了,但是进入节点solve(mid+1,rval,st+lt,ed)
之前就需要还原。回溯的时候我们再假设我们原封不动地将树状数组还给父亲就好了