题意
思考
今天练习的第二道模拟退火……
(WA) 了好几次发现是最后没有开根号!!
参考了一下 (attack) 的题解,主要思路是先随机分组,然后随机选一个数分到权值最小的组里来退火。(ps:玄学质数好用)
代码
#include<bits/stdc++.h>
using namespace std;
typedef double D;
const int N = 110;
int a[N], pos[N], cnt[N], n, m;
D sum[N], ans = 1e18, x, SUM, ANS = 1e18;
void SA(){
memset(sum, 0, sizeof(sum)); ANS = 0;
for(int i=1; i<=n; i++) pos[i] = rand() % m + 1, sum[pos[i]] += a[i];
for(int i=1; i<=m; i++) ANS += (sum[i] - x) * (sum[i] - x);
D t = 10000; int p, i;
while(t > 1e-14){
p = min_element(sum + 1, sum + m + 1) - sum;
i = rand() % n + 1;
D PANS = ANS;
ANS -= (sum[ pos[i] ] - x) * (sum[ pos[i] ] - x) + (sum[p] - x) * (sum[p] - x);
sum[ pos[i] ] -= a[i]; sum[p] += a[i];
ANS += (sum[ pos[i] ] - x) * (sum[ pos[i] ] - x) + (sum[p] - x) * (sum[p] - x);
if( (PANS > ANS) || (exp( (D)(ANS - PANS) / t) < ((D)rand() / (D)RAND_MAX)) ){
pos[i] = p;
}
else{
sum[p] -= a[i]; sum[pos[i]] += a[i]; ANS = PANS;
}
t *= 0.99;
}
if(ANS < ans) ans = ANS;
}
int main(){
srand(time(0)); srand(19260817); srand(rand());
cin >> n >> m;
for(int i=1; i<=n; i++){
cin >> a[i]; x += a[i];
}
x /= m;
int cnt = 1926;
while(cnt--){
SA();
}
cout << fixed << setprecision(2) << sqrt(ans / (D)m);
return 0;
}
总结
感觉这种随机化算法很玄学,考试的话还是建议先打完暴力分,如果实在没思路再考虑模拟退火,不过算法本身局限性也很大,适用于数据范围小一些的题,部分计算几何也可做。