模拟退火入门题*3

CTS2019Day2T1出了道可以乱搞的的计算几何,我辛辛苦苦写了280多行,结果就给了我10分,这不气炸了。
据我分析,可能是因为我没有学过模拟退火,乱搞是真的乱搞,儿切既没有经验也没有感觉:比如随机前20个最优性合并。
所以我就决定先把模拟退火学了。


这东西特别好理解,但是原理我是真的搞不懂,凭啥这样就行,而且关于接受为啥是\(e ^ {\frac{\Delta f}{T}}\),就更说不清了。
大体来讲,就是随机范围从最大开始不断缩小,如果当前随机的解比最优解优,那必定更新;否则有概率接受这个决策,而这个概率就是上面的那个式子。由此可见,这个概率还受温度影响,温度越低,接受的概率越小。
然后每一次随机后,\(T = T * Delta\)\(Delta\)一般取值0.97~0.999左右,然后到\(T < eps\)的时候退出,\(eps\)一般取\(1e-14\)吧。
接下来看几道入门题吧。


[JSOI2004]平衡点 / 吊打XXX
这道题大概就是模拟退火的经典题。
首先平衡点肯定是一个点,使\(\sum dis(i, x) * w_i\)最小(用力矩分析一下就差不多了这不是物理课)。
我们先在平衡点的基础上随机一个点,然后算上面那个值,如果比答案小,平衡点就更新为这个点;否则当前点有概率是这个点。这么搞好多次,结果就越来越接近了。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<ctime>
#include<assert.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-14;
const int maxn = 1e3 + 5;
const db DELTA = 0.994;
inline ll read()
{
  ll ans = 0;
  char ch = getchar(), last = ' ';
  while(!isdigit(ch)) last = ch, ch = getchar();
  while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
  if(last == '-') ans = -ans;
  return ans;
}
inline void write(ll x)
{
  if(x < 0) x = -x, putchar('-');
  if(x >= 10) write(x / 10);
  putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
  freopen(".in", "r", stdin);
  freopen(".out", "w", stdout);
#endif
}

int n;
struct Node
{
  db x, y; int w;
  friend In db dis(Node A, Node B)
  {
    return sqrt((A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y));
  }
}p[maxn];

db ansx, ansy, ans = 1e18;

In db Rand(db T) {return (rand() * 2 - RAND_MAX) * T;}
In db calc(db x, db y)
{
  Node t = (Node){x, y, 0};
  db ret = 0;
  for(int i = 1; i <= n; ++i) ret += dis(t, p[i]) * p[i].w;
  return ret;
}

In void HIAHIA()
{
  db T = 1900;
  while(T > eps)
    {
      db nx = ansx + Rand(T), ny = ansy + Rand(T);
      db nans = calc(nx, ny), del = nans - ans;
      if(del < 0)
	{
	  ansx = nx, ansy = ny;
	  ans = nans;
	}
      else if(exp(-del / T) * RAND_MAX > rand())	//把RAND_MAX除过来,就是一个0~1的数 
	ansx = nx, ansy = ny;
      T *= DELTA;
    }
}

int main()
{
  //MYFILE();
  n = read();
  srand(time(0));
  for(int i = 1; i <= n; ++i) p[i].x = read(), p[i].y = read(), p[i].w = read();
  for(int T = 1; T <= 30; ++T) HIAHIA();	//跑30次,提高准确率 
  printf("%.3lf %.3lf\n", ansx, ansy);
  return 0;
}

**[[HAOI2006]均分数据](https://www.luogu.org/problemnew/show/P2503)** 这题就更有意思了。 一个直观的想法就是我们让每一组数据尽量平均,但如果直接贪心的话肯定gg,所以我们模拟退火随机交换两个数,再贪心看解是否更优。 另一种做法是直接`random_shuffle`几次,每次贪心或dp求出最优解。 ```c++ #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; #define enter puts("") #define space putchar(' ') #define Mem(a, x) memset(a, x, sizeof(a)) #define In inline typedef long long ll; typedef double db; const int INF = 0x3f3f3f3f; const db eps = 1e-14; const int maxn = 25; const db DELTA = 0.997; In ll read() { ll ans = 0; char ch = getchar(), last = ' '; while(!isdigit(ch)) last = ch, ch = getchar(); while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar(); if(last == '-') ans = -ans; return ans; } In void write(ll x) { if(x < 0) x = -x, putchar('-'); if(x >= 10) write(x / 10); putchar(x % 10 + '0'); } In void MYFILE() { #ifndef mrclr freopen(".in", "r", stdin); freopen(".out", "w", stdout); #endif }

int n, m, a[maxn];
db ans = INF, ave;

In db TIME() {return (db)clock() / CLOCKS_PER_SEC;}

int b[maxn], c[maxn];
In db calc()
{
Mem(c, 0);
for(int i = 1; i <= n; ++i)
{
int pos = 1;
for(int j = 2; j <= m; ++j) if(c[j] < c[pos]) pos = j;
c[pos] += b[i];
}
db ret = 0;
for(int i = 1; i <= m; ++i) ret += (1.0 * c[i] - ave) * (1.0 * c[i] - ave);
ret = sqrt(ret / m);
return ret;
}
In void HIA()
{
db T = 1000;
while(T > eps)
{
int x = rand() % n + 1, y = rand() % n + 1;
while(y == x) y = rand() % n + 1;
swap(b[x], b[y]);
db tp = calc(), del = tp - ans;
if(del < 0) ans = tp;
else if(exp(-del / T) * RAND_MAX > rand()) swap(b[x], b[y]);
T *= DELTA;
}
}

int main()
{
//MYFILE();
srand(19260817);
n = read(), m = read();
for(int i = 1; i <= n; ++i) a[i] = read(), ave += a[i];
ave /= m; ans = calc();
while(TIME() < 0.8)
{
for(int i = 1; i <= n; ++i) b[i] = a[i];
HIA();
}
printf("%.2lf\n", ans);
return 0;
}

</br>
<font size = 3> **[luogu P2210 Haywire](https://www.luogu.org/problemnew/show/P2210)** </font>
这题和上一题一样,模拟退火随机交换两个数。
然后我们可以一直让程序跑到快超时为止。
```c++
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<stack>
#include<queue>
#include<assert.h>
#include<ctime>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-14;
const int maxn = 15;
const db DELTA = 0.997;
In ll read()
{
  ll ans = 0;
  char ch = getchar(), last = ' ';
  while(!isdigit(ch)) last = ch, ch = getchar();
  while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
  if(last == '-') ans = -ans;
  return ans;
}
In void write(ll x)
{
  if(x < 0) x = -x, putchar('-');
  if(x >= 10) write(x / 10);
  putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
  freopen(".in", "r", stdin);
  freopen(".out", "w", stdout);
#endif
}

int n, t[maxn][3], a[maxn];
int ans = 0;

In db TIME() {return 1.0 * clock() / CLOCKS_PER_SEC;}

In int calc()
{
  int ret = 0;
  for(int i = 1; i <= n; ++i)
    ret = ret + abs(a[i] - a[t[i][0]]) + abs(a[i] - a[t[i][1]]) + abs(a[i] - a[t[i][2]]);
  return ret >> 1;
}

In void HIA()
{
  db T = 1000;
  while(T > eps)
    {
      int x = rand() % n + 1, y = rand() % n + 1;
      while(x == y) y = rand() % n + 1;
      swap(a[x], a[y]);
      int tp = calc(), del = tp - ans;
      if(del < 0) ans = tp;
      else if(exp(-del / T) * RAND_MAX > rand()) swap(a[x], a[y]);
      T *= DELTA;
    }
}

int main()
{
  //MYFILE();
  srand(19260817);
  n = read();
  for(int i = 1; i <= n; ++i) t[i][0] = read(), t[i][1] = read(), t[i][2] = read();
  for(int i = 1; i <= n; ++i) a[i] = i;
  ans = calc();
  while(TIME() < 0.8)
    {
      for(int i = 1; i <= n; ++i) a[i] = i;
      HIA();
    }
  write(ans), enter;
  return 0;
}
posted @ 2019-05-16 23:03  mrclr  阅读(568)  评论(0编辑  收藏  举报