题解:AT_abc389_e [ABC389E] Square Price
前言
E 比 F 困难,怎么会是呢?
思路分析
首先考虑一种经典做法:将问题转化为物品体积为 \(p_i,3p_i,5p_i,7p_i \cdots\) 的 01 背包问题。因为物品价值都为 \(1\),所以可以贪心地选择体积前 \(k\) 小的物品。
然后问题转化到这一步就自然了,考虑二分出我们选与不选的物品体积的临界值,小于等于这个值的物品都选上。
已知临界值 \(mid\),对于每一个 \(p_i\),我们选择的物品数量 \(k\) 应该满足:
\[(2k-1)p_i\le mid
\]
放缩一下:
\[k \le \lfloor \frac{\lfloor \frac{mid}{p_i} \rfloor +1}{2} \rfloor
\]
这里有一个细节:在选完小于等于临界值的物品后,如果背包容量还有剩余,会考虑选择大于临界值的物品,特判一下即可。
总体复杂度 \(O(n \log m)\)。
代码实现
不知道在 check 时会不会炸 long long,所以开了 __int128。
#include<bits/stdc++.h>
using namespace std;
long long ans,p[200005],sum,m,n,maxn;
bool check(long long ans){
__int128 sum=0;
for(int i=1;i<=n;i++){
__int128 num=((ans/p[i]+1))/2;
sum+=num*num*p[i];
if(sum>m) return false;
}
return true;
}
long long find(long long l,long long r){
if(l==r) return l;
long long mid=(l+r+1)>>1;
if(check(mid)) return find(mid,r);
else return find(l,mid-1);
}
int main(){
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(long long i=1;i<=n;i++){
cin>>p[i];
}
ans=find(0,m);
for(long long i=1;i<=n;i++){
__int128 num=(ans/p[i]+1)/2;
sum+=num;
m-=num*num*p[i];
maxn=max((__int128)maxn,(2*num+1)*p[i]);
}
sum+=m/maxn;
cout<<sum;
return 0;
}