zoukankan      html  css  js  c++  java
  • 学习笔记:模拟退火

    模拟退火大概就是一个随机化求最优解的问题。

    考虑一个较连续的多峰的函数,用模拟退火可以较大几率找到极值,具体过程(这里假设找的是最小值,最大值反着弄一下就行了):

    1. 初始一个温度 / 步长 (T),随机一个点

    2. 在步长范围内随机选一个新点,记能量变化量 (ΔE) 为新点值 (-) 旧点值。

      • (ΔE < 0) 就跳到新点(更优)
      • 否则一定概率跳到新点(!)

      这个概率一般取 (e^{-frac{ΔE}{T}}),即判断 (e^{-frac{ΔE}{T}} > rand(0, 1)) 作为跳不跳的条件,(rand(0, 1)) 表示 (0 sim 1) 的随机数。

      (考虑 (< 0) 里面这个东西 (>1),如果 (>0) 那么它越靠近 (0) 概率越高。

    3. 缩小步长 (T) (一般乘一个 (0.99) 之类的东西),重复步骤 (2) 直到步长足够小(在输出精度误差范围内)。

    正确性十分玄学,不过在重复多次的情况下错误率极低(具体可以通过峰值,循环,幂次的形式自己算一算)

    例题

    POJ2420:二维自变量的连续函数,板子。

    #include <iostream>
    #include <cstdio>
    #include <ctime>
    #include <cmath>
    #include <cstdlib>
    using namespace std;
    
    const int N = 105;
    const double eps = 1e-5;
    
    double inline rand(int l, int r) {
        return (double)rand() / RAND_MAX * (r - l) + l; 
    }
    
    int n, X[N], Y[N];
    double ans = 1e18, s;
    
    double inline get(double x, double y) {
        double res = 0;
        for (int i = 1; i <= n; i++) res += sqrt((x - X[i]) * (x - X[i]) + (y - Y[i]) * (y - Y[i]));
        ans = min(ans, res);
        return res;
    }
    
    void inline simulateAnneal() {
        double x = rand(0, 10000), y = rand(0, 10000), v = get(x, y);
        for (double t = 10000; t > eps; t *= 0.99) {
            double nx = rand(x - t, x + t), ny = rand(y - t, y + t), nv = get(nx, ny);
            if (exp(-(nv - v) / t) > rand(0, 1)) x = nx, y = ny, v = nv; 
        }
    }
    
    int main() {
        s = clock();
        srand(time(0)); 
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%d%d", X + i, Y + i);
        while (((double)clock() - s) / CLOCKS_PER_SEC < 0.8) simulateAnneal();
        printf("%.0f
    ", ans);
    }
    

    AHOI2014 保龄球:交换两个位置差距不会太大较为连续,板子。

    #include <iostream>
    #include <cstdio>
    #include <ctime>
    #include <cstdlib>
    #include <cmath>
    using namespace std;
    
    const int N = 55;
    
    int n, X[N], Y[N], m, p[N];
    
    int ans = 0, sum;
    
    int inline get() {
        int res = sum;
        for (int i = 1; i < m; i++)
            if (X[p[i]] == 10) res += X[p[i + 1]] + Y[p[i + 1]];
            else if (X[p[i]] + Y[p[i]] == 10) res += X[p[i + 1]]; 
        ans = max(ans, res);
        return res;
    }
    
    double inline rd() {
        return (double)rand() / RAND_MAX;
    }
    
    void inline simulateAnneal() {
        int v = get();
        for (double t = 1e4; t > 1e-4; t *= 0.99) {
            int a = rand() % m + 1, b = rand() % m + 1;
            swap(p[a], p[b]);
            if (n + (X[p[n]] == 10) == m) {
                int nv = get();
                if (exp(-((double)v - nv) / t) <= rd()) swap(p[a], p[b]); 
                else v = nv;
            } else swap(p[a], p[b]);
        }
    }
    
    int main() {
        srand(time(0)); double s = clock();
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%d%d", X + i, Y + i);
        if (X[n] == 10) scanf("%d%d", X + n + 1, Y + n + 1);
        m = n + (X[n] == 10);
        for (int i = 1; i <= m; i++) sum += X[i] + Y[i], p[i] = i;
        while ((clock() - s) / CLOCKS_PER_SEC < 0.8) simulateAnneal();
        printf("%d", ans);
        return 0;
    }
    

    HAOI2006 均分数据:考虑一种贪心方式,每次按照一定顺序,每个数放最小和里,考虑肯定有顺序能构成最优解,而且这题数据水的一批,因此乱搞一下就过了。。

    #include <iostream>
    #include <cstdio>
    #include <ctime>
    #include <cstdlib>
    #include <cmath>
    using namespace std;
    
    const int N = 21;
    
    int n, m, a[N], b[N], p[N];
    
    double ans = 9e18;
    
    int inline get() {
    	for (int i = 1; i <= m; i++) b[i] = 0;
    	for (int i = 1; i <= n; i++) {
    		int t = 1;
    		for (int j = 2; j <= m; j++) if (b[j] < b[t]) t = j;
    		b[t] += a[p[i]];
    	}
    	double s = 0;
    	for (int i = 1; i <= m; i++) s += b[i];
    	s /= m;
    	double res = 0;
    	for (int i = 1; i <= m; i++) res += (b[i] - s) * (b[i] - s);
    	res /= m;
    	ans = min(ans, res);
    	return res;
    }
    
    double inline rd() {
    	return (double)rand() / RAND_MAX;
    }
    
    void inline simulateAnneal() {
    	int v = get();
    	for (double t = 1e3; t > 1e-2; t *= 0.9) {
    		int a = rand() % n + 1, b = rand() % n + 1;
    		swap(p[a], p[b]);
    		int nv = get();
    		if (exp(-((double)nv - v) / t) <= rd()) swap(p[a], p[b]); 
    		else v = nv;
    	}
    }
    
    int main() {
    	srand(time(0)); double s = clock();
    	scanf("%d%d", &n, &m);
    	for (int i = 1; i <= n; i++) scanf("%d", a + i), p[i] = i;
    	simulateAnneal();
    	printf("%.2f
    ", sqrt(ans));
    	return 0;
    }
    
  • 相关阅读:
    C语言——总结回顾
    C语言——第十四、十五周作业
    题目思路——统计素数并求和
    题目思路——单词长度
    C语言——第七周作业
    C语言——第六周作业
    C语言——第四次作业
    C语言——第三次作业
    C语言——第二次作业
    C语言——第一次作业
  • 原文地址:https://www.cnblogs.com/dmoransky/p/14274471.html
Copyright © 2011-2022 走看看