题目:https://www.acwing.com/problem/content/2682/
题意:给n个数,分成m组,使得每一组的和的均值最小,2<=m<=,m<=n<=20
题解:模拟退火+贪心,直接模拟退火暴力所有情况,可能或超市,可以尝试一下用贪心优化一下模拟退火,就是一部分情况用模拟退火来做,一部分情况可以用贪心来做,某些题可以用dp等等(补充在下面),这个题外层可以用模拟退火来随机这个排列,check里面用一个贪心,在这个排列的情况下,从左往右依次将每一个数放到和最小的组里面。
(一般用在check里面做也就是这个代码中的calc。就是模拟退火随机一个条件,另一个条件在check里面用一个贪心或者其它做法来优化,一定要保证这样做的情况下,一定能枚举到所有的状态,只不过因为贪心做法,那些不好的情况就不要了,比如说这个题,在这个排列下从左往右,把每一个数放到最小的那个组里面,可能最优答案是这个数放到的不是我这次贪心放的那个组,我贪心这么放,是因为到这个数放了,这个组的和最小,它只能放在这里,但是我一定有一种排列,可以让我这个贪心规则把这个数放到最优答案的那个组里,所以是可以枚举到所有情况的,但是很多不可能是答案的情况,比如一个最小的数一个组,这种情况直接忽略了(当然在一些特殊样例中,最优答案确实可能是一个最小的数一个组,但在那种样例的情况下,贪心规则+随机排列也一定能枚举到这种情况),能够比较接近答案的所有情况都可能枚举到,这样才能保证模拟退火+贪心能够得到最优答案)
#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <functional>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
//#include <unordered_map>
//#include <unordered_set>
//#include <bits/stdc++.h>
//#define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
inline int read() {
char ch = getchar();
int s = 0, w = 1;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0', ch = getchar();
}
return s * w;
}
const int N = 25, M = 10;
int n, m;
int w[N], s[M];
double ans = 1e8;
double calc() {
memset(s, 0, sizeof(s));//清空,s代表m个组,每一个组中的和
for (int i = 0; i < n; i++) {
int k = 0; //暴力寻找最小的那组
for (int j = 0; j < m; j++) {
if (s[j] < s[k]) k = j;
}
s[k] += w[i];//叠加给它
}
double avg = 0; //求均值
for (int i = 0; i < m; i++) avg += (double)s[i] / m;
double res = 0;
for (int i = 0; i < m; i++) res += (s[i] - avg) * (s[i] - avg);
res = sqrt(res / m);
ans = min(ans, res);//保存最小值
return res;
}
void simulate_anneal() {
random_shuffle(w, w + n);//随机排列这个序列
for (double t = 1e4; t > 1e-6; t *= 0.99) {
int a = rand() % n, b = rand() % n;//随机找到俩个交换
double x = calc();//贪心求出这个排列下的最优解
swap(w[a], w[b]);//交换
double y = calc();//再求
double delta = y - x;//作差
//跳不过去就再交换回来
if (exp(-delta / t) < (double)rand() / RAND_MAX) swap(w[a], w[b]);
}
}
signed main() {
srand((unsigned)time(NULL));
n = read(), m = read();
for (int i = 0; i < n; i++) cin >> w[i];
//调用100次
for (int i = 0; i < 100; i++) simulate_anneal();
printf("%.2lf\n", ans);
vector<int>vec;
random_shuffle(vec.begin(),vec.end());
return 0;
}