- 目标:求函数极值。
- 思想:函数上随机跳跃,跳跃的幅度由温度限制(温度逐步递减)。
- 流程:设温度 \(T\) 从初始 \(T_0\) 到 \(T_f\) 降温速度 \(0<rate<1\) 通常取 \([9.5,9.99]\) 。
每次随机求得新的函数值,如果函数值优于原函数值,直接更新状态。否则,设差量为\(\Delta\ge 0\),则有 \(e^{-\frac{\Delta}{T}}\) 的概率更新 [关键]。
然后多跑几次这个流程可以加上卡时: \(clock()=CLOCKS\_PER\_SEC\) 时为 \(1\) 秒。
玄学算法,会流程就行了
考场上只会20pt
首先操作是交换差分数组,由于要使得方差尽量大,差分数组显然是先减后增的。
执行模拟退火过程,每次随机交换两个值(由于上面的性质,一个非最小值它能换到的位置为数组的另一端且唯一确定),所以随机一个值就可以了,看看代码就会了。
- code
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
const int N = 1e5 + 5;
const db tlim = CLOCKS_PER_SEC * 0.97;
mt19937 myrand(time(0));
ll a[N], b[N], c[N], mn;
int n, cnt0;
ll _pw(ll x) {return x * x;}
ll calc() {
ll res = 0, cur = 0, _ = 0;
for(int i = 1; i < n; i++) {cur += b[i]; _ += cur;}
_ /= n;
cur = 0; res = _pw(_); //a[0]=0
for(int i = 1; i < n; i++) {cur += b[i]; res += _pw(cur - _);}
return res / n;
}
db T;
ll ans, res;
void Work() {
int k = myrand() % (n - cnt0 - 1) + 1;
int lmn = 0, rmn;
for(int i = 1; i < n; i++) {
c[i] = b[i];
if(b[i] == mn) {
if(!lmn) lmn = i; rmn = i;
}
}
if(k < lmn) {
ll val = b[k];
for(int j = n - 1; j; j--) if(b[j] <= val) {
for(int i = k; i < j; i++) b[i] = b[i + 1]; b[j] = val;
break;
}
}
else {
k += cnt0;
ll val = b[k];
for(int j = 1; j < n; j++) if(b[j] <= val) {
for(int i = k; i > j; i--) {b[i] = b[i - 1];} b[j] = val;
break;
}
}
ll res_ = calc(), delta = res_ - res; ans = min(ans, res_);
if(delta < 0 || exp(-delta / T) * UINT_MAX > myrand()) {res = res_;}
else {for(int i = 1; i < n; i++) b[i] = c[i];}
}
void SA() {
res = calc();
if(!ans) ans = res; else ans = min(ans, res);
for(T = 1e6; T > 1e-5; T *= 0.98) {Work();}
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {scanf("%lld", &a[i]); a[i] *= n;}
for(int i = 1; i < n; i++) {c[i] = a[i + 1] - a[i];}
sort(c + 1, c + n); mn = c[1];
for(int l = 1, r = n - 1, i = n - 1; i; i--) {
if(c[i] == mn) cnt0++;
if(i & 1) b[l++] = c[i];
else b[r--] = c[i];
}
auto len = clock(); //long: clock()
t_fire();
len = clock() - len; //time for once
while(clock() + len < tlim) {SA();}
for(int t = 1; t <= 100; t++) {Work();}
printf("%lld\n", ans);
return 0;
}