P8632 [蓝桥杯 2015 国 B] 居民集会 题解
其实我也没写什么正经做法……不过能过题
对集会位置退火,考虑对任意集会位置 $a,b,c$ 怎么 $O(1)$ 计算答案。
以 $[1,a]$ 的家庭为例,其贡献为 $\sum\limits_{i=1}^at_i(d_a-d_i)=d_a\sum\limits_{i=1}^a t_i-\sum\limits_{i=1}^ad_it_i$。
分别预处理 $\sum\limits_{i=1}^a t_i$ 和 $\sum\limits_{i=1}^ad_it_i$ 即可。$(a,b],(b,c],(c,n+1]$ 的家庭同理。注意 $d_{n+1}=L$。
初始解贪心地选择人数最多的三个位置。
设当前温度为 $T$,则对每个集会位置 $x\gets (x+pT+n)\bmod n+1$ 生成新解,其中 $p$ 为 $[-1,1]$ 的随机实数。
调一个合适的参数,然后微调初温,发现初温为 $n+1$ 和 $n+2$ 时可以得到两个不同的 90 分。
所以分别跑 600 遍取 $\min$ 即可。
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
int n, L, x, y, z, X, Y, Z, d[100050], t[100050];
long long q, Q, s[100050], p[100050];
bool u;
inline long long F(int a, int b, int c)
{
int x = a, y = b, z = c;
if (x > y)
swap(x, y);
if (x > z)
swap(x, z);
if (y > z)
swap(y, z);
return d[x] * s[x] - p[x] + d[y] * (s[y] - s[x]) - p[y] + p[x] + d[z] * (s[z] - s[y]) - p[z] + p[y] + L * (s[n] - s[z]) - p[n] + p[z];
}
inline void SA()
{
double T = n + u + 1;
while (T > 1e-7)
{
int a = int(x + (rand() * 2.0 / RAND_MAX - 1) * T + n) % n + 1,
b = int(y + (rand() * 2.0 / RAND_MAX - 1) * T + n) % n + 1,
c = int(z + (rand() * 2.0 / RAND_MAX - 1) * T + n) % n + 1;
long long f = F(a, b, c);
double d = f - q;
if (d < 0 || exp(-d / T) * RAND_MAX > rand())
q = f, x = a, y = b, z = c;
Q = min(Q, q);
T *= 0.9965;
}
}
int main()
{
srand(388651);
scanf("%d%d", &n, &L);
for (int i = 1; i <= n; ++i)
{
scanf("%d%d", d + i, t + i);
s[i] = s[i - 1] + t[i];
p[i] = p[i - 1] + 1ll * d[i] * t[i];
if (t[i] > t[x])
z = y, y = x, x = i;
else if (t[i] > t[y])
z = y, y = i;
else if (t[i] > t[z])
z = i;
}
q = Q = F(X = x, Y = y, Z = z);
for (int i = 0; i < 600; ++i)
SA();
u = 1;
q = F(x = X, y = Y, z = Z);
srand(388651);
for (int i = 0; i < 600; ++i)
SA();
printf("%lld", Q);
return 0;
}