• 目标:求函数极值。
  • 思想:函数上随机跳跃,跳跃的幅度由温度限制(温度逐步递减)。
  • 流程:设温度 \(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;
}