题解:AT_abc389_e [ABC389E] Square Price

· · 题解

其实这个套路挺经典的。

买一个物品共 k 件需要花 k^2P 元,可以转换为:

已经买了 k-1 件这个物品,再买一件,需要加价 (k^2-(k-1)^2)P=(2\times k-1)P 元。

物品价格以平方速度增长,所以最多购买 $O(\sqrt M)$ 个物品,因此时间复杂度 $O(N\sqrt M)$,使用优先队列优化可以做到 $O((\log N)\sqrt M)$,代码如下: ```cpp #include<bits/stdc++.h> using namespace std; #define int long long int n,M; __int128 m; __int128 p[200005]; struct node{ int id; __int128 up,cnt; bool operator< (const node &b) const{ return up>b.up; } }; priority_queue<node> Q; signed main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin>>n>>M; m=M; for(int i=1;i<=n;i++){ int x;cin>>x; p[i]=x; Q.push({i,p[i],1}); } int ans=0; while(1){ auto u=Q.top(); Q.pop(); if(m<u.up) break; ans++; m-=u.up; u.up=(2*u.cnt+1)*p[u.id]; u.cnt++; Q.push(u); } cout<<ans<<endl; return 0; } ``` 当然这个复杂度不足以通过此题,我们重新考察这个贪心的过程,每次加价的价格单调不降(即上述代码第 $32$ 行 `u.up` 单调不降),而每次加价会使答案增加 $1$,因此答案随最后一个加价价格的增加而增加。 因此我们考虑二分答案,二分最后一个加价的价格,之后算出每个物品的最大购买件数,检查总费用是否 $\le M$ 即可。 设最后加价价格为 $mx$,则对于物品 $i$,有 $(2\times k-1)P\le mx$,即 $k\le \frac{mx+P}{2P}$。 求出最后加价价格 $mx$ 后,批量处理每个物品购入 $\frac{mx+P}{2P}$ 件。 ```cpp #include<bits/stdc++.h> using namespace std; #define int long long int n,m,p[200005]; bool ck(int mx){ __int128 pri=0; for(int i=1;i<=n;i++){//p(2*mid-1)<=mx __int128 t=(mx+p[i])/(2*p[i]); t*=t; if(t>(__int128)m) return false; t*=p[i]; if(t>(__int128)m) return false; pri+=t; } return pri<=m; } int getans(int mx){ int ans=0; for(int i=1;i<=n;i++){//2*mid-1<=mx ans+=(mx+p[i])/(2*p[i]); } return ans; } signed main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin>>n>>m; for(int i=1;i<=n;i++){ cin>>p[i]; } int l=1,r=m,res=0; while(l<=r){ int mid=(l+r)/2; if(ck(mid)){ res=mid; l=mid+1; }else{ r=mid-1; } } cout<<getans(res)<<"\n"; return 0; } ``` 然后你发现你 WA 了两个点,考虑这个例子: ``` 2 25 1 1 ``` 最优解是第一个物品购买 $3$ 个,第二个物品购买 $4$ 个,最后加价价格为 $16-9=7$。 但是如果最后加价价格为 $7$,二分检查时程序会认为每个物品都购买了 $4$ 个,从而返回 `false`。 因此我们得到的最后加价价格上界为 $6$,于是最后批量处理时程序会认为每个物品只能买 $3$ 个,少算了 $1$ 个,然后就 WA 了。 解决方法很简单,少算当且仅当两个物品加价价格一样,因此每个物品最多少算 $1$ 个,答案最多少算 $N$ 个,在批量处理之后,计算出剩余钱数,然后用优先队列处理少算的部分即可。 时间复杂度 $O(N\log M+N\log N)$,可以通过此题。 ```cpp #include<bits/stdc++.h> using namespace std; #define int long long int n,m,p[200005]; bool ck(int mx){ __int128 pri=0; for(int i=1;i<=n;i++){//检查总费用是否小于等于 M __int128 t=(mx+p[i])/(2*p[i]); t*=t; if(t>(__int128)m) return false; t*=p[i]; if(t>(__int128)m) return false; pri+=t; } return pri<=(__int128)m; } struct node{ int id; __int128 up,cnt; bool operator< (const node &b) const{ return up>b.up; } }; priority_queue<node> Q; int getans(int mx){ int ans=0; for(int i=1;i<=n;i++){//批量处理 int t=(mx+p[i])/(2*p[i]); ans+=t; m-=t*t*p[i]; Q.push({i,(__int128)p[i]*(2*t+1),t+1}); } while(m){//处理少算的部分 auto u=Q.top(); Q.pop(); if(m<u.up) break; ans++; m-=u.up; u.up=(__int128)(2*u.cnt+1)*p[u.id]; u.cnt++; Q.push(u); } return ans; } signed main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin>>n>>m; for(int i=1;i<=n;i++){ cin>>p[i]; } int l=1,r=m,res=0; while(l<=r){//二分最后加价的价格 int mid=(l+r)/2; if(ck(mid)){ res=mid; l=mid+1; }else{ r=mid-1; } } cout<<getans(res)<<"\n"; return 0; } ```