现有 n 个糖果,每个糖果有两个数值为美味度 ai 和美观度 bi。现要拿出不超过 ⌊n/2⌋+1 个糖果使得它们的美味度和以及美观度大于所有糖果美味度的和以及美观度的和的二分之一,输出所取的编号,顺序任意。若无解,输出 -1。
其中:
1≤n≤105,ai,bi≤109
我已经使用贪心算法 AC 此题,赛时我的退火骗了 70 分,后来我骗到了 93 分,只差一个点就过,我已经调参无数,卡常到底,现来求助是否可以从退火本身下手,优化复杂度
主函数代码:
ll n,sum1,sum2,nowmei,nowlook,ansmei,anslook;
struct node{
ll mei,look,num;//美味度,美观度及编号
}a[100005],b[50005];//B数组作为答案数组,只需要开到 n 的一半即可。
//省略主函数定义
srand(time(0));
n=read();
for(int i=1;i<=n;++i){
a[i].mei=read();
a[i].num=i;
sum1+=a[i].mei;
}
for(int i=1;i<=n;++i){
a[i].look=read();
sum2+=a[i].look;
}//输入同时处理出整体上的美味度和以及美观度和
SA();
if(ansmei*2<sum1||anslook*2<sum2){
puts("-1");
return 0;
}//若我们发现最优状态任无法满足,输出无解
write(n/2+1);putchar('\n');
for(int i=1;i<=n/2+1;i++){
write(b[i].num);putchar(' ');
}//输出编号
退火代码:
map<ll,ll>mp;//用来标记已经被选取的下标
const double delta=0.997;
void SA(){
double T=1e4;//此题无需最优解,温度按喜好定
while(T>1e-3){
mp.clear();
nowmei=0,nowlook=0;//循环不清空,_____
ll lastmei=ansmei;//储存上一个解
ll lastlook=anslook;
memset(b,0,sizeof(b));
for(int i=1;i<=n/2+1;i++){
ll lucky=rand()%n+1;//选取随机数
while(true){
lucky=rand()%n+1;
if(!mp[lucky]&&lucky<=n&&lucky>0)break;
}//防止随机数发生重复与越界
mp[lucky]++;//标记已选取的下标
b[i]=a[lucky];//进行拷贝
nowmei+=b[i].mei;//进行和的累加
nowlook+=b[i].look;
}
int de1=nowmei-lastmei;//计算能量差
int de2=nowlook-lastlook;
if(de1>0||de2>0){
ansmei=nowmei;
anslook=nowlook;
if(ansmei*2>sum1&&anslook*2>sum2)return;//若新解满足要求,直接退出
}else{
if(exp((-de1-de2)/T)-RAND_MAX>rand()){
ansmei=nowmei;
anslook=nowlook;
}
}
T*=delta;
}
}