均分数据(模拟退火&贪心)

题目: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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值