具体题目见洛谷 P1337 [JSOI2004]平衡点 / 吊打XXX
方法一:模拟退火
思路:当绳子平衡时,系统的能量最小,则此时物体总的重力势能要最小,也就是物体重量一定的情况下绳长最长,即桌子上的绳长最短。使用模拟退火算法,设定适当的系数,产生新解时生成[-t*RAND_MAX,t*RAND_MAX)
的随机变动范围(rand():[0,RAND_MAX)
)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
const int T0 = 3000;//起始温度,要足够大
const double Tk = 1e-15;//终止温度,要足够小
const double delta = 0.996;//降温系数,要接近1
int n;
struct node { int x, y, w; }obj[maxn];
double ansX, ansY, ansEnergy;//最终答案
double energy(double x, double y) {//总能量越小越稳定
double energy = 0;
for (int i = 1; i <= n; i++) {
double dx = x - obj[i].x;
double dy = y - obj[i].y;
energy += sqrt(dx * dx + dy * dy) * obj[i].w;
}
return energy;
}
void SA(){//模拟退火
double t = T0;//温度要足够高
while (t > Tk) {//略大于0
//随机产生新解,生成[-t*RAND_MAX,t*RAND_MAX)的随机变动范围(rand():[0,RAND_MAX))
double newX = ansX + (rand() * 2 - RAND_MAX) * t;
double newY = ansY + (rand() * 2 - RAND_MAX) * t;
double newEnergy = energy(newX, newY);
double deltaE = newEnergy - ansEnergy;
if (deltaE < 0)//newEnergy<ansEnergy 如果此答案更优则接受
ansX = newX, ansY = newY, ansEnergy = newEnergy;
else if (exp(-deltaE / t) * RAND_MAX > rand())//否则根据多项式概率接受
ansX = newX, ansY = newY;//不更新ansEnergy
t *= delta;//降温
}
}
void solve() {
ansX /= n, ansY /= n;//以平均数作为初始答案
ansEnergy = energy(ansX, ansY);
SA();//SA();SA();可以多跑几遍退火,增加得到最优解的概率
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> obj[i].x >> obj[i].y >> obj[i].w;
ansX += obj[i].x; ansY += obj[i].y;
}
solve();
printf("%.3lf %.3lf\n", ansX, ansY);
return 0;
}
关于调参:
- 如果答案不是最优:
①增大 Δ Δ Δ;
②调整 T 0 , T k T_0,T_k T0,Tk。 T 0 , T k T_0,T_k T0,Tk 需要根据值域对应调整,保证每次随机出的变化幅度不会跑太远
③多跑几遍SA()
④尝试更换随机数种子,或者srand(rand())
。总之,总有可能跑出正解。 - 如何产生新解:
①坐标系内:随机生成一个点,或者生成一个向量;
②序列问题:random_shuffle
或随机交换两个数;
③网格问题:看作二维序列,每次交换两个格子。