抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

小星野战力那么强一个人一组就够了ᕕ(◠ڼ◠)ᕗ

您已获得最佳的阅读体验!以及上面的小彩蛋……

我们会发现这道题和 P5785 任务安排 长得神似。两道题都是把一个连续的序列分成若干块(块数没有特殊限制)。这启发我们把状态设置为 dp[i],含义是:“把前 个士兵全部分好的最大修正战斗力”。

现在我们着手设计一个暴力 DP 程序,分析如下图:

因为 部分已知,就是我们的 dp[j];我们重点需要解决的就是 部分的计算。

考虑到题面行动队战斗力的定义涉及到部分元素的和,因此预先维护整个序列的前缀和 ,于是 。把待求区间分成一组,初始战斗力就是 。本题还涉及到一个“修正战斗力”,且题目要求求解修正战斗力的最大值,不妨令 ,那么该组的修正战斗力就是

暴力 DP 代码就可以写出来了,时间复杂度 。预计 ,就不放了。


看到题面的 ,正解的时间复杂度很可能是 的。暴力程序的性能瓶颈卡在变量 的循环上,因此考虑优化 的求解,此时就开始了本题的重头戏——斜率优化/凸包优化

把需要最大化的算式拿出来分析,我们进行化简可以得到:

进行斜率优化时,我们把 看作主元,并且希望让斜率仅和 有关。那我们移项可以得到:

类比直线的斜截式方程 ,发现这个式子可以用数形结合的方式做出来:斜率 ,截距 ,纵坐标 ,横坐标 。任务是最大化 dp[i] 的值,考虑到 ,所以只要构造的直线的截距最大,dp[i] 就是最大的。

对于特定斜率的直线,显然是靠上端的直线截距更大,如下图(若绘图有误烦请指出):

对于不同斜率的点,截距最大的直线所过的点均在一个上凸包上(红线连出):

同时,当前直线所过的点是第一个斜率比当前直线斜率小的点。“最小值最大”,那不就是二分嘛!别急,这道题可以不用二分,有更简便的方法。

考虑到每次代入的点 ,因为每个士兵的战斗力均为正数,显然它的前缀和不可能出现下降趋势,是单调递增的。又知道 是递增枚举的,所以每次构造的直线的斜率是单调递减的。因此考虑用单调队列维护这个合法点,具体操作如下:

  1. 当队头点的斜率大于了当前直线的斜率,显然不会再被用到,直接弹出。形式化地,当 时弹队头。
  2. 当队尾点的斜率小于将要加入的点 的斜率,原先的队尾已经不满足上凸包“斜率单调递减”的要求了,故弹出。也就是说当 时就弹出队尾。

至此,单调队列的队头就是我们所维护的 。把 代入状态转移方程里即可计算出答案了(注意超时的原因是暴力找 ,和状态转移方程本身没关系),由于涉及斜率的计算,要使用浮点数(或者你也可以就使用整型,斜率换成交叉相乘)。

代码:

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
51
52
53
54
55
#include <bits/stdc++.h>

#define N 1000010
using namespace std;

typedef long long ll;

double dp[N];
double x[N], sumX[N];
double a, b, c;
int q[N];
int hh = 0, tt = 0;

double getY(int i) {
// 获得纵坐标的值
return dp[i] + a * sumX[i] * sumX[i] - b * sumX[i];
}

double slope(int x1, int x2) {
// 获取两点斜率
return (getY(x1) - getY(x2)) * 1.0 / (sumX[x1] - sumX[x2]) * 1.0;
}

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);

int n, t;
cin >> t;
while (t--) {
// 多测清空
memset(dp, 0, sizeof dp);
memset(x, 0, sizeof x);
memset(sumX, 0, sizeof sumX);
hh = 0, tt = 0; // 初始时队列里已有一个元素0

cin >> n >> a >> b >> c;
for (int i = 1; i <= n; i++) {
cin >> x[i];
sumX[i] = sumX[i - 1] + x[i]; // 预处理前缀和
}
q[0] = 0;
for (int i = 1; i <= n; i++) {
while (hh < tt && slope(q[hh + 1], q[hh]) >= 2 * a * sumX[i]) hh++; // 不满足单调性,弹出
int j = q[hh]; // 维护的点
double X = sumX[i] - sumX[j]; // 我们定义的X,用来简化计算
dp[i] = dp[j] + a * X * X + b * X + c; // 代入状态转移方程
while (hh < tt && slope(q[tt], q[tt - 1]) <= slope(i, q[tt])) tt--; // 不满足上凸包性质,弹出
q[++tt] = i; // 插入新点
}
cout << (ll) dp[n] << endl; // 注意转型成整型
}
return 0;
}

评论