bzoj 2428 [HAOI2006]均分数据

Description



已知N个正整数:A1、A2、……、An 。今要将它们分成M组,使得各组数据的数值和最平均,即各组的均方差最小

Solution



这题一眼看不会做啊,这题肯定是乱搞题啊。。。一看这 n20 ,想搜来着,然后感觉不太对。看了一眼别人的题解恍然大悟,模拟退火!真是长姿势了,以前总是拘泥于用退火做计算几何,没有深入理解退火的实质。真是too young。
先随机分组,然后随机选择一个元素x,当温度T比较高的时候说明答案并不稳定,这样贪心找一个组 sum 最小的组把x放进去;当温度T较小时说明答案比较稳定,随机一个组放进去即可
这里有些不理解的地方,因为退火在更改后状态不是更优的时候是有一定概率转移的,而这道题如果当转移状态后答案不是更优的时候我采取干脆不转移的方式跑的反而更快。而在计算几何也中通常采取转移后没有变的更优就干脆不转移的方式。到底要不要在答案没变优的时候还以一定概率转移呢?= =

Code

#include 
using namespace std;
typedef long long LL;
#define pb push_back
#define mp make_pair
#define F first
#define S second
inline void read(int &t) {
    int f = 1;char c;
    while (c = getchar(), c < '0' || c > '9') if (c == '-') f = -1;
    t = c - '0';
    while (c = getchar(), c >= '0' && c <= '9') t = t * 10 + c - '0';
    t *= f;
}
const int N = 25;
int n, m, pos[N];
double a[N], sum[N];
int main() {
    srand(65535);
    read(n), read(m);
    double avg = 0.0;
    for (int i = 1; i <= n; ++i) scanf("%lf", &a[i]), avg += a[i];
    random_shuffle(a + 1, a + n + 1);
    avg /= m;
    double t = 1e20;
    for (int i = 1; i <= 3000; ++i) {
        memset(sum, 0, sizeof(sum));
        double ans = 0;
        for (int j = 1; j <= n; ++j) pos[j] = rand() % m + 1, sum[pos[j]] += a[j];
        for (int j = 1; j <= m; ++j) ans += (sum[j] - avg) * (sum[j] - avg);
        for (double T = 2000; T >= 0.1; T *= 0.9) {
            int x = rand() % n + 1, p = pos[x], y;
            if (T > 500) y = min_element(sum + 1, sum + m + 1) - sum;
            else y = rand() % m + 1;
            if (p == y) continue;
            double pre = ans;
            ans -= (sum[p] - avg) * (sum[p] - avg);
            ans += (sum[p] - a[x] - avg) * (sum[p] - a[x] - avg);
            ans -= (sum[y] - avg) * (sum[y] - avg);
            ans += (sum[y] + a[x] - avg) * (sum[y] + a[x] - avg);
            sum[p] -= a[x], sum[y] += a[x];
            t = min(t, ans);
            if (ans <= pre)  pos[x] = y;
            else sum[p] += a[x], sum[y] -= a[x], ans = pre;//不转移
        /*  else if (rand() >= exp((ans - pre) / T)) {一定概率转移
                sum[p] += a[x], sum[y] -= a[x];
                ans = pre;
            }
            else pos[x] = y;*/
        }
    }
    printf("%.2lf\n", sqrt(t / m));
    return 0;
}

你可能感兴趣的:(bzoj题解,随机,模拟退火)