[洛谷P3878][TJOI2010]分金幣
阿新 • • 發佈:2018-12-03
題目大意:把$n(n\leqslant30)$個數分成兩組,兩組個數最多相差$1$,求出兩組元素差的絕對值最小使多少
題解:模擬退火
卡點:$\exp$中的兩個數相減寫反,導致$\exp(x)$中的$x>0$,$\exp(x)>1$,相當於一直接受生成的解
C++ Code:
#include <algorithm> #include <cstdio> #include <cmath> #define maxn 32 inline long long abs(long long a) {return a > 0 ? a : -a;} int T, n, divn; long long sum; const double ST = 100, delT = 0.9992, eps = 1e-5; int Tim = 20; struct node { int s[maxn]; long long w; } ans; inline long long calc(node &x) { long long __sum = 0; for (int i = 0; i < divn; i++) __sum += x.s[i]; x.w = abs(sum - __sum - __sum); if (n & 1) x.w = std::min(x.w, abs(sum - __sum - __sum - x.s[divn] - x.s[divn])); return x.w; } inline double rand_d() {return static_cast<double> (rand()) / RAND_MAX;} void SA() { double T = ST; node now = ans, nxt; while (T > eps) { nxt = now; int x = rand() % n, y = rand() % n; T *= delT; if (x == y) continue; std::iter_swap(nxt.s + x, nxt.s + y); long long del = calc(nxt); if (del < now.w || exp((now.w - del) / T) > rand_d()) now = nxt; if (del < ans.w) ans = nxt; } } int main() { srand(20040826); scanf("%d", &T); while (T --> 0) { scanf("%d", &n); divn = n >> 1; sum = 0; for (int i = 0; i < n; i++) scanf("%d", ans.s + i), sum += ans.s[i]; if (n == 1) { printf("%d\n", *ans.s); continue; } std::random_shuffle(ans.s, ans.s + n); calc(ans); for (int i = 0; i < Tim; i++) SA(); printf("%lld\n", ans.w); } return 0; }