[BZOJ 2428] [HAOI2006] 均分数据
题目链接:BZOJ - 2428
题目分析
这道题使用随机化算法来做,可以使用模拟退火,也可以random_shuffle之后贪心。
模拟退火:
要进行多次模拟退火,每次进行模拟退火之前,给每个点随机分配一下集合。
然后模拟退火的随机移动就是随机一个点,再找另一个集合y,将这个点移动到集合y中。
开始时模拟退火的移动导致答案的变动幅度会比较大,这时候集合y就直接取最小的集合,到后期就随机一个集合y。
注意,如果移动之后答案与当前答案相同,也要移动过去。这是为了避免滞留在平坦的高原地形上。
random_shuffle + 贪心:
多次进行random_shuffle + 贪心的过程,可能就会找到最优解了= =
代码
模拟退火:
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; const int MaxN = 20 + 5; typedef double DB; int n, m; int A[MaxN], Belong[MaxN]; DB Ans, Ave; DB Sum[MaxN]; inline DB Sqr(DB x) {return x * x;} inline DB Rand() { return (DB)(rand() % 10000) / 10000.0; } void SA() { memset(Sum, 0, sizeof(Sum)); for (int i = 1; i <= n; ++i) { Belong[i] = rand() % m + 1; Sum[Belong[i]] += (DB)A[i]; } DB T = 10000; int t, x, y; DB DE, Temp, Num; Num = 0; for (int i = 1; i <= m; ++i) Num += Sqr(Sum[i] - Ave); if (Num < Ans) Ans = Num; while (T > 0.1) { T *= 0.9; t = rand() % n + 1; x = Belong[t]; if (T > 500) { y = 1; Temp = Sum[1]; for (int i = 2; i <= m; ++i) { if (Sum[i] < Temp) { Temp = Sum[i]; y = i; } } } else y = rand() % m + 1; if (y == x) continue; Temp = Num; Temp -= Sqr(Sum[x] - Ave) + Sqr(Sum[y] - Ave); Temp += Sqr(Sum[x] - A[t] - Ave) + Sqr(Sum[y] + A[t] - Ave); if (Temp < Ans) Ans = Temp; DE = Num - Temp; if (DE >= 0 || Rand() < exp(DE / T)) { Num = Temp; Sum[x] -= A[t]; Sum[y] += A[t]; Belong[t] = y; } } if (Num < Ans) Ans = Num; } int main() { srand(80458946); scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) { scanf("%d", &A[i]); Ave += (DB)A[i]; } Ave /= m; Ans = 1e50; for (int i = 1; i <= 10000; ++i) SA(); printf("%.2lf\n", sqrt(Ans / m)); return 0; }
random_shuffle + 贪心:
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; const int MaxN = 20 + 5; typedef double DB; int n, m; int A[MaxN]; inline DB Sqr(DB x) {return x * x;} DB Ave, Ans; DB Solve() { DB p, q, Sum = 0, ret = 0, Cnt = 1; int i; for (i = 1; i <= n && Cnt < m; ++i) { if (Sum + (DB)A[i] >= Ave) { p = Sqr(Sum + (DB)A[i] - Ave); q = Sqr(Sum - Ave); if (p < q) { ret += p; Sum = 0; } else { ret += q; Sum = A[i]; } ++Cnt; } else Sum += (DB)A[i]; } for (; i <= n; ++i) Sum += (DB)A[i]; ret += Sqr(Sum - Ave); return sqrt(ret / m); } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) { scanf("%d", &A[i]); Ave += (DB)A[i]; } Ave /= m; Ans = 1e50; for (int i = 1; i <= 500000; ++i) { random_shuffle(A + 1, A + n + 1); Ans = min(Ans, Solve()); } printf("%.2lf\n", Ans); return 0; }