洛谷 P3878 [TJOI2010]分金币

题目大意

将$n$个数分成两半,使得这两半的差尽量小

Solution

我们首先先把这$n$个数按下标顺序分成两组,然后每次随机选取前半段和后半段的两个数将其交换,如果更优的话就更新$ans$,否则就以$e^{\frac{-de}{t}}$($de=$当前解-最优解)的概率接受该交换(其实就是模拟退火的基本套路)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
#define ll long long
const int MaxN = 50;
const double delta = 0.993;
ll n, a[MaxN], ans;
ll abs(ll x){ return (x > 0) ? x : (-x);}
inline ll calc()
{
ll sum1 = 0, sum2 = 0;
for (int i = 1; i <= n; i++)
{
if(i <= (n + 1) / 2)
sum1 += a[i];
else sum2 += a[i];
}
return abs(sum1 - sum2);
}
inline void sa()
{
double t = 10000000;
while (t > 1e-14)
{
int x = rand() % ((n + 1) / 2) + 1, y = rand() % ((n + 1) / 2) + ((n + 1) / 2);
std::swap(a[x], a[y]);
int now = calc();
int de = now - ans;
if (de < 0)
ans = now;
else if (exp(-de / t) * RAND_MAX <= rand())
std::swap(a[x], a[y]);
t *= delta;
}
}
int main()
{
int T;
srand(time(NULL));
scanf("%d", &T);
while (T--)
{
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
ans = 1e9;
for (int i = 1; i <= 50; i++)
sa();
printf("%lld\n", ans);
}
return 0;
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×