题解:P12052 [THUPC 2025 决赛] 图,距离,最优化
好玩。
但是关于大数放两边的结论,怎么没有严谨证明啊,来补一个。
思路
我们不难发现,排成一条链一定是优秀的,因为这样任意两点之间的最短路才能尽可能大。
问题是怎么排呢?我们考虑重新写出该式子。设
其中
这毫无疑问是一个进步,但是我们仍然不知道怎么排列最好。
考虑到:如果交换
若
注意到随着
最优的
\{y_i\} 没有局域极大值。或者说,
\{y_i\} 一定是先减后增。
这毫无疑问给了我们启发。不妨从大往小填数,那么一定是从两边往中间填,已经填入的部分是一个前缀和一个后缀。如下:
xxx...xx???...??xxx...xx
其中 x 是已填入的,? 是未填入的。
在填入下一个数时(用 ! 表示,假设它填在前缀,且是第
xxx...xx!??...??xxx...xx
我们可以直接计算贡献
填在后面也是同样的。不多论述。注意只要填完前
设
设当前填入的数为
- 填入前缀,则产生贡献
j\times (s_n-j) 。 - 填入后缀,则产生贡献
(p_i-j)\times (s_n-p_i+j) 。
于是得出转移式:
只需
如果只用一个滚动数组(类似于背包),请注意特判
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 305, maxv = 2005;
int a[maxn];
ll dp[maxn * maxv];
int main(){
int T;
scanf("%d", &T);
while(T --){
int n;
scanf("%d", &n);
int sum = 0;
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), sum += a[i];
for(int i = 1; i <= sum; i ++) dp[i] = -1;
sort(a + 1, a + n + 1);
int pre = 0;
for(int t = n; t >= 2; t --){
pre += a[t];
for(int i = pre; i >= 0; i --){
if(a[t] > 0){
if(dp[i] >= 0) dp[i] += 1ll * (pre - i) * (sum - pre + i);
if(i >= a[t] && dp[i - a[t]] >= 0) dp[i] = max(dp[i], dp[i - a[t]] + 1ll * i * (sum - i));
}else if(dp[i] >= 0) dp[i] += max(1ll * (pre - i) * (sum - pre + i), 1ll * i * (sum - i));
}
}
ll ans = 0;
for(int i = 0; i <= sum; i ++) ans = max(ans, dp[i]), dp[i] = 0;
printf("%lld\n", ans);
}
return 0;
}