题解 CF1344D 【Résumé Review】
提供一个和官方题解不同的算法。
注意到如果把题目要求中的“整数”和
求可导的多元函数的最大值,一般都可以用拉格朗日乘数法来搞(维基百科)。对于这道题而言,可以列出这个方程组:
化简一下就是:
发现因为
由于是单调的,可以二分
然后考虑加回去
如果在前一步二分的时候,把二分的下界定为
所以我们在外层再套一个二分,二分最优解中是最小的
现在求出的方案是满足限制的实数最优解。感性理解一下就知道整数的最优解
这一步很简单,把每一个
最后把
这就是我比赛现场的做法。这个做法得到了TLE on test 10。赛后发现调整几个精度问题就过了:
- 内层二分的结束条件是
r-l\le\epsilon ,这个\epsilon=1 。因为二分边界最大可以达到3\times10^{18} ,此时long double的精度只到个位,如果\epsilon 比精度还要小就会死循环。 - 最后一步按照增加的贡献排序时,不能用
long double算出来两个贡献再相减,要手动化简一下式子。因为两个相近的大数相减会大大降低精度。
代码:
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
using ld=long double;
int n,_,pos[1<<17];ll k,a[1<<17],b[1<<17];
struct st{ll x;int y;}c[1<<17],q[1<<17];
int chk(int x){
ld l=-1e20,r=1e20,ik=k;int ok=0;
for(int i=1;i<=x;i++)ik-=a[i];
if(ik<0)return 0;
for(int i=x+1;i<=n;i++)l=max(l,(ld)a[i]-3*a[i]*a[i]),r=min(r,(ld)a[i]);
while(r-l>1){
ld mid=(l+r)/2,tot=0;
for(int i=x+1;i<=n;i++)tot+=sqrtl((a[i]-mid)/3);
if(tot>ik)l=mid,ok=1;else r=mid;
}
for(int i=x+1;i<=n;i++)b[i]=sqrtl((a[i]-l)/3);
return !ok;
}
int main(){
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++)scanf("%lld",a+i),c[i]={a[i],i};
sort(c+1,c+1+n,[](st a,st b){return a.x<b.x;});
for(int i=1;i<=n;i++)pos[c[i].y]=i;
sort(a+1,a+1+n);
int l=0,r=n+1,mid;
while(l<r){
mid=l+r>>1;
if(chk(mid))l=mid+1;
else r=mid;
}
for(int i=1;i<=l;i++)b[i]=a[i];
chk(l);
for(int i=1;i<=n;i++){k-=b[i];if(b[i]<a[i])q[++_]={a[i]-3*b[i]*b[i]-3*b[i],i};}
sort(q+1,q+1+_,[](st a,st b){return a.x>b.x;});
for(int i=1;i<=k;i++)b[q[i].y]++;
for(int i=1;i<=n;i++)printf("%lld%c",b[pos[i]]," \n"[i==n]);
return 0;
}