二至济南--随机化
\(update: 2025/9/20\)
随机化并不是作为一节课讲的,而是在一次机缘巧合中学到的
爬山算法
大抵是有了新欢便放弃了旧爱 (虽然我从未了解过爬山算法,毕竟我接触到的第一个随机化算法是退火)
模拟退火
随机化的一种算法,当一个问题的方案数极大且不是一个单峰函数时使用 (虽然我都在骗分中使用)
在爬山算法中,我们会舍去次解,但有时最优解由这些次解得到,而模拟退火会以一定概率接受这些次解,从而转移到更优的状态中
具体的,模拟退火分为以下几点
参数
有如下几个
- \(T\) 温度
- \(L\) 马尔可夫链长度(在同一温度下循环几次,下面代码循环为1所以没写)
- \(lt\) 降温参数 ( 其实是\(\Delta T\) )
- \(et\) 终止温度 ( \(T_{end}\) )
- \(step\) 随机数范围
- \(cur\) 当前保存的解
- \(ans1\) 新解
- \(ans\) 最终解
原谅作者的取名技术
接受准则
模拟退火中,用温度来反映当前状态的"活跃度",即接受劣解的概率,一般用\(Metropolis\)准则,如下(截取自OI_Wiki并改编):
\[P=\begin{Bmatrix}
1 & ans1\: is\: better\:than \: cur\\
e^{\frac{\Delta E}{T} } & else
\end{Bmatrix}
\]
其中,\(\Delta E\)是新解与保存解的差值(相对于更优而言,保证其为负数即可)
其他技巧
一个卡时小技巧 (截取自OI_Wiki)
clock()
可以返回程序运行时间(毫秒),(double)clock()/CLOCKS_PER_SEC()
是当前程序的用时(秒)
\(code\)
P4653 [CEOI 2017] Sure Bet
//May all the beauty be blessed.
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
double a[100010],b[100010],ans;
void sa(){
double T=1e5,et=1e-7,lt=0.99997;//初始化
int x=rand()%(n+1),y=rand()%(n+1);
double cur=min(a[x]-x-y,b[y]-x-y); //先得出一个解
while(T>et){
int step=(int)(T/1e5*n)+1; //计算范围,其随着T的减小而降低
int o=rand()%2;
if(o){
int x2=x+rand()%(2*step+1)-step;
if(x2<0) x2=0;
if(x2>n) x2=n;
double ans1=min(a[x2]-x2-y,b[y]-x2-y);
if(ans1>cur||exp((ans1-cur)/T)*RAND_MAX>rand()) x=x2,cur=ans1; //Metropolis准则
}else{
int y2=y+rand()%(2*step+1)-step;
if(y2<0) y2=0;
if(y2>n) y2=n;
double ans1=min(a[x]-x-y2,b[y2]-x-y2);
if(ans1>cur||exp((ans1-cur)/T)*RAND_MAX>rand()) y=y2,cur=ans1;
}
ans=max(ans,cur);
//将上述代码运行L次(马尔可夫链长度),再降温
T*=lt;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
srand(time(0));
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i]>>b[i];
sort(a+1,a+n+1,greater<>());
sort(b+1,b+n+1,greater<>());
for(int i=1;i<=n;i++) a[i]+=a[i-1],b[i]+=b[i-1];
while((double)clock()/CLOCKS_PER_SEC<1.95) sa();//卡时小技巧
cout<<fixed<<setprecision(4)<<ans;//保留小数
}