模拟退火

模拟退火

  • 很多时候我们会被要求求一些函数的最值问题,但是又因为值域很大,是连续的乃至无穷的,那么搜索是搜不出来的,对于这种问题,一般来说爬山算法是很可以的,比如下边的图

  • 但是爬山往往会被困在一个局部最优解上出不来。例如下边的图

  • 所以就需要用到了模拟退火,一个比贪心还贪的东西

简介

模拟退火算法(\(Simulated Annealing,SA\))最早的思想是由\(N. Metropolis\) 等人于\(1953\)年提出。\(1983\) 年,\(S. Kirkpatrick\) 等成功地将退火思想引入到组合优化领域。它是基于\(Monte-Carlo\)迭代求解策略的一种随机寻优算法,其出发点是基于物理中固体物质的退火过程与一般组合优化问题之间的相似性。模拟退火算法从某一较高初温出发,伴随温度参数的不断下降,结合概率突跳特性在解空间中随机寻找目标函数的全局最优解,即在局部最优解能概率性地跳出并最终趋于全局最优。模拟退火算法是一种通用的优化算法,理论上算法具有概率的全局优化性能,目前已在工程中得到了广泛应用,诸如\(VLSI\)、生产调度、控制工程、机器学习、神经网络、信号处理等领域。模拟退火算法是通过赋予搜索过程一种时变且最终趋于零的概率突跳性,从而可有效避免陷入局部极小并最终趋于全局最优的串行结构的优化算法。

  • 总而言之就是通过一定的概率跳出局部最优解,再凭借着你的脸概率稳定在全局最优解,当然,有的时候一个好的参数是可以稳定\(AC\)的。

变量介绍

  • 几个参数:当前最优解\(S_{0}\)​,新解\(S\),解变动量\(\Delta S\)\(S\)\(S_{0}\)的差),上一个被接受的解\(S_{1}\)​,初温\(T_{st}\)​,末温\(T_{ed}\),当前温度\(T\),温度变动量\(\Delta T\)

算法流程

  • 首先看一张经典的图。

  • 这张图非常好的体现了模拟退火的过程,从 \(T_{st}\) 开始,每次乘上\(\Delta T\)得到\(T\),如果\(T\)小于\(T_{ed}\)则终止降温。\(T_{st}\)一般设置在 $ 1000 \sim 6000 $ 左右,\(\Delta T\)则是一个略小于1的常数,而\(T_{ed}\)一般设置在\(1e-6 \sim 1e-15\)之间。

  • 在降温的过程中,我们在\(S_{1}\)(不是最优解\(S_{0}\))的基础上跳跃产生新解\(S\),需要注意的是跳跃范围随温度的降低而变小,在温度高的时候,解的变化量非常大,这时的任务是在全局范围中尽量找到最优解的大概位置,随着温度的降低,解渐渐稳定,这时的任务是稳定在最优解的准确位置。

  • 对于新解的接受准则,就需要用到\(Metropolis\)接受准则

  • 假设我们的目标是求最小值,如果\(S\)\(S_{0}\)​的差,也就是\(\Delta S\)小于\(0\),我们就接受当前解,因为它现在是最优解。

  • 但是如果\(\Delta S\)大于\(0\),也就是我们遇到了一个更劣的解,我们也要以一定的概率来接受它,因为我们要找的一个多峰函数的全局最小值,因此就不能局限于一个局部的凹函数。而这个概率是\(exp⁡(−\Delta E/T)\)

  • 通俗解释就是对于\(\Delta S\),如果它较大,那么我们遇到了一个很劣的解,那我们接受它的概率就相对较小,因为\(-\Delta S\)更小。而如果\(\Delta S\)较小,即我们遇到了一个比较劣的解,我们接受它的概率就比较大,因为\(- \Delta S\)较大。对于\(T\),随着时间的增加,\(T\)越来越小,因此我们做除法之后,这样接受的概率就随着温度的降低而减小,因为\(\Delta S\)是一个负数。

  • 而对于整个式子,当\(T\)较大的时候,我们会接受大部分的解,当\(T\)较小的时候,我们就只会接受\(\Delta S\)小的解。。如果选择接受\(S\),则把\(S_{1}\)​设置为\(S\),然后降温找下一个解。

题目讲解

  • P2503
  • 直接随机出每个数的组别,然后随机换组就好了,只要退火退的足,正解啥的都能出来
here
#include <bits/stdc++.h>
#define LL long long
#define Re register int
#define LD double
#define mes(x, y) memset(x, y, sizeof(x))
#define cpt(x, y) memcpy(x, y, sizeof(x))
#define fuc(x, y) inline x y
#define fr(x, y, z)for(Re x = y; x <= z; x ++)
#define fp(x, y, z)for(Re x = y; x >= z; x --)
#define delfr(x, y, z)for(Re x = y; x < z; x ++)
#define delfp(x, y, z)for(Re x = y; x > z; x --)
#define frein(x) freopen(#x ".in", "r", stdin)
#define freout(x) freopen(#x ".out", "w", stdout)
#define ki putchar('\n')
#define fk putchar(' ')
#define WMX aiaiaiai~~
#define pr(x, y) pair<x, y>
#define mk(x, y) make_pair(x, y)
#define pb(x) push_back(x)
#define re(x) return x
#define sec second
#define fst first
using namespace std;
namespace kiritokazuto{
    fuc(char, getc)() {
        static char buf[1 << 18], *p1, *p2;
        if(p1 == p2){p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin);if(p1 == p2)return EOF;}
        return *p1 ++;
    }
    fuc(LL, read)() {
        LL x = 0, f = 1; char c = getc();
        while(!isdigit(c)){if(c == '-')f = -1;c = getc();}
        while(isdigit(c))x = (x << 1) + (x << 3) + (c ^ 48), c = getc();
        return x * f;
    }
    template <typename T> fuc(void, write) (T x){
        if (x < 0)putchar('-'), x = -x;
        if (x > 9)write(x / 10);putchar(x % 10 | '0');
    }

}

using namespace kiritokazuto;

const int maxn = 5e6 + 10000,  Mod = 1e9 + 7;
const LL Inf = 2147483647;
LD dert = 0.992;
int n, m;
LD avl;
LD ans = Inf;
random_device seed;
mt19937 wmx(seed());
fuc(int, rand)(int x, int y) {
    uniform_int_distribution<> dis(x, y);
    return dis(wmx);
}
int bel[26];
LD tmp_ans = Inf;
int sum[26];
int a[26];
fuc(LD , get_tmp_ans)() {
    LD res = 0;
    fr(i, 1, m) {res += (avl - sum[i]) * (avl - sum[i]);}
    return res / m;
}
fuc(void, change)(int x, int y) {
    sum[bel[x]] -= a[x];
    sum[y] += a[x];
    bel[x] = y  ;
}
fuc(void, SA)() {
    LD T = 3000;
    LD tmp_ans = ans;
    while(T > 1e-10) {
        // if((LD)clock() / CLOCKS_PER_SEC >= 0.99) {
        //     printf("%.2lf", sqrt(ans));
        //     exit(0);
        // }
        int pos1 = rand() % n + 1, pos2 = rand() % m + 1;
        int old = bel[pos1];
        change(pos1, pos2);
        LD tmp = get_tmp_ans();
        LD dis = tmp - tmp_ans;
        if(dis < 0) {
            tmp_ans = tmp;
            if(tmp_ans < ans)ans = tmp_ans;
        }else if(exp( - dis / T) * RAND_MAX <= rand()) {
            change(pos1, old);
        }
        T *= dert;
    }
}

signed main() {
    srand(time(0));
    n = read(), m = read();
    fr(i, 1, n){
        a[i] = read();
        avl += a[i];
    }
    avl /= m;
    fr(i ,1 ,n){
        bel[i] = rand() % m + 1;
        sum[bel[i]] += a[i];
    }
    ans = get_tmp_ans();
    fr(i, 1, 1000) SA();
    // while(1)SA();
    printf("%.2lf", sqrt(ans));
    return 0;
}


  • P7926
  • 首先观察出交换查分数组的性质,以及最终答案多是由先减后增的查分数组序列贡献的,然后先构造出一个单谷的查分数组序列,在此基础上交换数据就能\(AC\)
here
#include <bits/stdc++.h>
#define LL long long
#define Re register int
#define LD double
#define mes(x, y) memset(x, y, sizeof(x))
#define cpt(x, y) memcpy(x, y, sizeof(x))
#define fuc(x, y) inline x y
#define fr(x, y, z)for(Re x = y; x <= z; x ++)
#define fp(x, y, z)for(Re x = y; x >= z; x --)
#define delfr(x, y, z)for(Re x = y; x < z; x ++)
#define delfp(x, y, z)for(Re x = y; x > z; x --)
#define frein(x) freopen(#x ".in", "r", stdin)
#define freout(x) freopen(#x ".out", "w", stdout)
#define ki putchar('\n')
#define fk putchar(' ')
#define WMX aiaiaiai~~
#define pr(x, y) pair<x, y>
#define mk(x, y) make_pair(x, y)
#define pb(x) push_back(x)
#define re(x) return x
#define sec second
#define fst first
using namespace std;
namespace kiritokazuto{
    fuc(char, getc)() {
        static char buf[1 << 18], *p1, *p2;
        if(p1 == p2){p1 = buf, p2 = buf + fread(buf, 1, 1 << 18, stdin);if(p1 == p2)return EOF;}
        return *p1 ++;
    }
    fuc(LL, read)() {
        LL x = 0, f = 1; char c = getc();
        while(!isdigit(c)){if(c == '-')f = -1;c = getc();}
        while(isdigit(c))x = (x << 1) + (x << 3) + (c ^ 48), c = getc();
        return x * f;
    }
    template <typename T> fuc(void, write) (T x){
        if (x < 0)putchar('-'), x = -x;
        if (x > 9)write(x / 10);putchar(x % 10 | '0');
    }

}

using namespace kiritokazuto;

const int maxn = 5e6 + 10000,  Mod = 1e9 + 7;
const LL Inf = 2147483647;
LL dis;
LD dert = 0.999;
random_device seed;
mt19937 wmx(seed());
int rand(int x,int y){
    uniform_int_distribution<> dis(x,y);
    return dis(wmx);
}

fuc(bool, cmp)(int x, int y){return x > y;}
LL c[maxn];
LL a[maxn];
int n;
LL res, sum, avl;
LL ans = Inf;
fuc(LL, get_tmp_ans)() {
    res = 0, sum = 0;
    fr(i, 2, n) a[i] = a[i - 1] + c[i], sum += a[i];
    sum += a[1];
    fr(i, 1, n) res += (a[i] * n - sum) * (a[i] * n - sum);
    // cerr << "Res = " << res / n << "\n";
    return res / n; 
}
fuc(void, SA)() {
    LD T = 2222;
    while(T > 1e-6) {
        if((LD)clock()/CLOCKS_PER_SEC >= 0.99){write(ans), exit(0);}
        int pos1 = rand(2, n);
        int pos2 = rand(2, n);
        while(pos2 == pos1)pos1 = rand(2, n);
        // cerr <<"x = " << pos1 << " y = " << pos2 << "\n";
        swap(c[pos1], c[pos2]);
        LL tp = get_tmp_ans();
        if(tp < ans ){
            ans = tp;
        }else if(exp((LD)(ans - tp) / T) * RAND_MAX <= wmx()) {
            swap(c[pos1], c[pos2]);
        }
        T *= dert;
    }
}
signed main() {
    n = read();
    fr(i, 1, n){
        a[i] = read();
        c[i] = a[i] - a[i - 1];
    }
    sort(c + 2, c + (n / 2) + 1, cmp);
    sort(c + (n / 2) + 1, c + n + 1);
    while(1)SA();
}


  • P3878

  • 这个就直接换呗..

here
#include <bits/stdc++.h>
#define LL long long
#define Re register int
#define LD double
#define mes(x, y) memset(x, y, sizeof(x))
#define cpt(x, y) memcpy(x, y, sizeof(x))
#define fuc(x, y) inline x y
#define fr(x, y, z)for(Re x = y; x <= z; x ++)
#define fp(x, y, z)for(Re x = y; x >= z; x --)
#define delfr(x, y, z)for(Re x = y; x < z; x ++)
#define delfp(x, y, z)for(Re x = y; x > z; x --)
#define frein(x) freopen(#x ".in", "r", stdin)
#define freout(x) freopen(#x ".out", "w", stdout)
#define ki putchar('\n')
#define fk putchar(' ')
#define WMX aiaiaiai~~
#define pr(x, y) pair<x, y>
#define mk(x, y) make_pair(x, y)
#define pb(x) push_back(x)
#define re(x) return x
#define sec second
#define fst first
using namespace std;
namespace kiritokazuto{
    auto read = [](){
        LL x = 0;
        int f = 1;
        char c;
        while (!isdigit(c = getchar())){ if (c == '-')f = -1; }
        do{ x = (x << 1) + (x << 3) + (c ^ 48); } while (isdigit(c = getchar()));
        return x * f;
    };
    template  <typename T> fuc(void, write)(T x){
        if (x < 0)putchar('-'), x = -x;
        if (x > 9)write(x / 10); putchar(x % 10 | '0');
    }
}

using namespace kiritokazuto;

const int maxn = 2e5 + 100,  Mod = 998244353;
const LL Inf = 2147483647;
LL t, ans, n, mid, odd;
LL a[maxn];
LL sum1, sum2;
random_device seed;
mt19937 wmx(seed());
LD dis;
LD dert = 0.9009;

fuc(void, SA)() {
    LD Tord = 2005;
    while(Tord > 1e-15) {
        int pos1 = (int)(wmx() % mid) + 1, pos2 = (n - (int)(wmx() % (mid + odd)));
        LD tmp = abs(sum1 - (a[pos1] << 1) + (a[pos2] << 1) - sum2);
        dis = tmp - ans;
        if(dis < 0) {
            ans = tmp;
            sum1 = sum1 - a[pos1] + a[pos2];
            sum2 = sum2 - a[pos2] + a[pos1];
            swap(a[pos1], a[pos2]);
        }else if(exp((dis / Tord)) < wmx() % 33333) {
            sum1 = sum1 - a[pos1] + a[pos2];
            sum2 = sum2 - a[pos2] + a[pos1];
            swap(a[pos1], a[pos2]);
        }
        Tord *= dert;
    }
}
signed main() {
    t = read();
    while(t --) {
        n = read();
        mid = n / 2;
        odd = n & 1;
        sum1 = sum2 = 0;
        // write(mid);
        // ki;
        fr(i, 1, n){
            a[i] = read();
            if(i <= mid)sum1 += a[i];
            else sum2 += a[i];
        }
        // write(sum1), ki, write(sum2), ki;
        ans = abs(sum1 - sum2);
        if(n == 1){printf("%lld\n", a[1]);continue;}
        fr(i, 1, 19)SA();
        write(ans);
        ki;
    }
}


posted @ 2022-11-03 09:46  kiritokazuto  阅读(146)  评论(4编辑  收藏  举报