模拟退火学习笔记
What is %你退火
说到%你退火我就会想到一个人,那就是\(S.Kirkpatrick, C.D.Gelatt\)和\(M.P.Vecchi\)。(wy2024届传奇oi/数学大师,@yanxu_cn)
模拟退火是一种基于物理冶金学中固体物质退火过程的启发式优化算法。它是一种全局优化算法,通常用于求解复杂的组合优化问题。该算法的灵感来自金属热处理过程中的退火过程,其中金属被加热然后缓慢冷却,以减少内部应力并改善其结晶结构。
在模拟退火算法中,系统状态被视为一个解,该解对应于问题的一个潜在解决方案。算法通过在解空间中进行随机游走来搜索最优解。在搜索过程中,算法允许接受劣质解的概率随时间逐渐降低,以避免陷入局部最优解。————GPT
%你退火 简称 SA
%你退火 Can Do What
很简单,不会做的时候乱搞,骗分。但是对题目的要求是,问题验证很简单,复杂度较低,而求解答案比较难。主要是代码也比较好写,这种三段式的结构也非常清晰,调参只在你追求更多分数是才能用到,这可比你瞎糊个暴力要强。
%你退火's trick
我其实也没学退火多久,水平很差,但也享受到了调参的乐趣,和我学数位dp差不多。
那么一份SA代码大概长这样
#include <bits/stdc++.h>
using namespace std;
const double delta=......;//降温系数,越接近1,SA结果越精确,但时间也会更多
double .........,answ,ansx,ansy;//一大堆变量
int solve(......){//估价函数,就是判断答案是否更优(这里我反而觉得是最不好想的地方,
//因为你要优化时间)
...
...
...
}
void sa(){
double x=ansx,y=ansy,end=......;//每次SA时要临时记录一下当前做到的地方,和ansx,
//ansy不同的是它会跳出去,所以可能近期会跟劣但
//这也是SA能处理多峰的关键
for(int t=.....;t>=end;t*=delta){//初温,就是你一开始波动会多大,这个可以让你在
//做多次SA时能跳出原来的最优解
int tx=.....;//根据当前的t来rand()决定与x,y差多少
int ty=.....;
int new_ans=solve(tx,ty);//计算新解
int Delta=new_ans-answ;
if(Delta<0){//若更优,则更新
answ=new_ans;
ansx=x=tx;
ansy=y=ty;
.......
}
else if(exp(-Delta/t)*RAND_MAX<rand()){//否则以一定概率接受新解,此时不能更
//新ans(用脚想,虽然我之前错了几次)
x=tx;
y=ty;
......
}
}
}
int main(){
srand(time(0));
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin....
...
...
...
while((double)clock()/CLOCKS_PER_SEC<0.96){//用来卡时,保险一点开0.8,平时做题我喜欢卡满
sa();
}
cout<<answ;
return 0;
}
实战演练(会一直更新)
主要包括一些调参方法和如何用SA
1.——————————————————————————
时间:3h左右
这题就是解析式一顿乱算这也是我第一次不看tj思路切掉紫题
#include<bits/stdc++.h>
using namespace std;
struct line{
double xi,yi,xj,yj;//左右
double k,b;
}p[105];
const double delta=0.99993;//重点!!!因为这题对精度的要求比较高,所以退
//火系数也要高一点
int n;
double ansx,ansy,answ;
line work(double a,double b,double c,double d){
line p;
if(a==c){//特判横的和竖的
p.k=114514;
p.b=114514;
if(b>d) p.xi=a,p.yi=b,p.xj=c,p.yj=d;
else p.xi=c,p.yi=d,p.xj=a,p.yj=b;
return p;
}
if(b==d){
p.k=-114514;
p.b=-114514;
if(a<c) p.xi=a,p.yi=b,p.xj=c,p.yj=d;
else p.xi=c,p.yi=d,p.xj=a,p.yj=b;
return p;
}
p.k=(d-b)/(c-a);
p.b=b-a*p.k;
if(a<c) p.xi=a,p.yi=b,p.xj=c,p.yj=d;
if(a>c) p.xi=c,p.yi=d,p.xj=a,p.yj=b;
return p;
}
double sq(double x){
return x*x;
}
double dis(double a,double b,double c,double d){
return sqrt(sq(a-c)+sq(b-d));
}
double calc(double x,double y,line p){
line q;
if(p.b==114514&&p.k==114514){
if(y>p.yi) return dis(x,y,p.xi,p.yi);
else if(y<p.yj) return dis(x,y,p.xj,p.yj);
else return fabs(x-p.xi);
}
if(p.b==-114514&&p.k==-114514){
if(x<p.xi) return dis(x,y,p.xi,p.yi);
else if(x>p.xj) return dis(x,y,p.xj,p.yj);
else return fabs(y-p.yi);
}
q.k=-1/p.k;
q.b=y-q.k*x;
double xi=(q.b-p.b)/(p.k-q.k),yi;
yi=xi*p.k+p.b;
if(xi<p.xi) return dis(p.xi,p.yi,x,y);
else if(xi>p.xj) return dis(x,y,p.xj,p.yj);
else return dis(x,y,xi,yi);
}
double solve(double x,double y){
double tot=0;
for(int i=1;i<=n;i++){
tot=max(tot,calc(x,y,p[i]));
}
return tot;
}
void sa(){
double end=1e-10,x=ansx,y=ansy;
for(double t=300;t>=end;t*=delta){//波动少一点
double tx=x+t*(2.0*rand()-RAND_MAX)/11451;//重点!!!!!因为这题
//坐标范围小于等于1000,所以每次跳的可以少一点
double ty=y+t*(2.0*rand()-RAND_MAX)/11451;
double new_ans=solve(tx,ty);
double Delta=new_ans-answ;
if(Delta<0){
answ=new_ans;
x=ansx=tx;
y=ansy=ty;
}else if(exp(-Delta/t)*RAND_MAX>rand()){
x=tx,y=ty;
}
}
}
int main(){
srand(time(0));
cin>>n;
for(int i=1;i<=n;i++){
cin>>p[i].xi>>p[i].yi>>p[i].xj>>p[i].yj;
p[i]=work(p[i].xi,p[i].yi,p[i].xj,p[i].yj);
ansx+=p[i].xi;
ansx+=p[i].xj;
ansy+=p[i].yi;
ansy+=p[i].yj;
}
ansx/=(n*2);
ansy/=(n*2);
answ=solve(ansx,ansy);
while((double)clock()/CLOCKS_PER_SEC<1.7){//这题时间开了2s
sa();
}
printf("%.15lf",answ);
return 0;
}
好久没更
2.xj出锅题
时间1h
主要是理解题意比较久。根据题意,我们容易用\(O(n^{2})\)预处理,然后用\(O(n)\)写出一个solve函数,没什么技术含量,但这是在OI赛制中的第一次应用
#include<bits/stdc++.h>
using namespace std;
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
/* --------------- fast io --------------- */ // begin
namespace Fread {
const int SIZE = 1 << 21;
char buf[SIZE], *S, *T;
inline char getchar() {
if (S == T) {
T = (S = buf) + fread(buf, 1, SIZE, stdin);
if (S == T) return EOF;
}
return *S++;
}
} // namespace Fread
namespace Fwrite {
const int SIZE = 1 << 21;
char buf[SIZE], *S = buf, *T = buf + SIZE;
inline void flush() {
fwrite(buf, 1, S - buf, stdout);
S = buf;
}
inline void putchar(char c) {
*S++ = c;
if (S == T) flush();
}
struct NTR {
~ NTR() { flush(); }
}ztr;
} // namespace Fwrite
#define getchar Fread :: getchar
#define putchar Fwrite :: putchar
namespace Fastio {
struct Reader {
template<typename T>
Reader& operator >> (T& x) {
char c = getchar();
T f = 1;
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
x = 0;
while (c >= '0' && c <= '9') {
x = x * 10 + (c - '0');
c = getchar();
}
x *= f;
return *this;
}
Reader& operator >> (char& c) {
c = getchar();
while (c == '\n' || c == ' ') c = getchar();
return *this;
}
Reader& operator >> (char* str) {
int len = 0;
char c = getchar();
while (c == '\n' || c == ' ') c = getchar();
while (c != '\n' && c != ' ') {
str[len++] = c;
c = getchar();
}
str[len] = '\0';
return *this;
}
Reader(){}
}cin;
const char endl = '\n';
struct Writer {
template<typename T>
Writer& operator << (T x) {
if (x == 0) { putchar('0'); return *this; }
if (x < 0) { putchar('-'); x = -x; }
static int sta[45];
int top = 0;
while (x) { sta[++top] = x % 10; x /= 10; }
while (top) { putchar(sta[top] + '0'); --top; }
return *this;
}
Writer& operator << (char c) {
putchar(c);
return *this;
}
Writer& operator << (char* str) {
int cur = 0;
while (str[cur]) putchar(str[cur++]);
return *this;
}
Writer& operator << (const char* str) {
int cur = 0;
while (str[cur]) putchar(str[cur++]);
return *this;
}
Writer(){}
}cout;
} // namespace Fastio
#define cin Fastio :: cin
#define cout Fastio :: cout
#define endl Fastio :: endl
/* --------------- fast io --------------- */ // end
const double delta=0.997;
int answ,ans[1005];
struct node{
int x,y;
}a[1005];
int n;
vector<int>v[1005];
vector<int>g[1005];
bool vis[1005];
void dfs(int u){
vis[u]=true;
for(int i=0;i<v[u].size();i++){
int to=v[u][i];
if(vis[to]==false){
dfs(to);
}
}
}
int solve(int a[]){
for(int i=1;i<=n;i++) vis[i]=false;
int tot=0;
for(int i=1;i<=n;i++){
if(vis[a[i]]==false){
for(int j=0;j<g[a[i]].size();j++){
vis[g[a[i]][j]]=true;
}
tot++;
}
}
return tot;
}
void sa1(){
double end=1e-6;
int ansx[1005];
for(int i=1;i<=n;i++){
ansx[i]=ans[i];
}
for(int t=1000;t>=end;t*=delta){
int tx=rand()%n+1;
int ty=rand()%n+1;
while(tx==ty){
tx=rand()%n+1;
ty=rand()%n+1;
}
swap(ansx[tx],ansx[ty]);
int new_ans=solve(ansx);
double Delta=new_ans-answ;
if(Delta<0){
answ=new_ans;
for(int i=1;i<=n;i++) ans[i]=ansx[i];
}else if(exp(-Delta/t)*RAND_MAX>=rand()){
swap(ansx[tx],ansx[ty]);
}
}
}
void sa2(){
double end=1e-6;
int ansx[1005];
for(int i=1;i<=n;i++){
ansx[i]=ans[i];
}
for(int t=1000;t>=end;t*=delta){
int tx=rand()%n+1;
int ty=rand()%n+1;
while(tx==ty){
tx=rand()%n+1;
ty=rand()%n+1;
}
swap(ansx[tx],ansx[ty]);
int new_ans=solve(ansx);
double Delta=new_ans-answ;
if(Delta>0){
answ=new_ans;
for(int i=1;i<=n;i++) ans[i]=ansx[i];
}else if(exp(-Delta/t)*RAND_MAX>=rand()){
swap(ansx[tx],ansx[ty]);
}
}
}
int main(){
freopen("broadcast.in","r",stdin);
freopen("broadcast.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i].x-a[i].y<=a[j].x&&a[i].x+a[i].y>=a[j].x){
v[i].push_back(j);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) vis[j]=false;
dfs(i);
for(int j=1;j<=n;j++){
if(vis[j]==true){
g[i].push_back(j);
}
}
ans[i]=i;
}
answ=solve(ans);
//cout<<answ<<endl;
while((double)clock()/CLOCKS_PER_SEC<0.5){
sa1();
}
cout<<answ<<endl;
while((double)clock()/CLOCKS_PER_SEC<0.93){
sa2();
}
cout<<answ<<endl;
return 0;
}