抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

题目传送门

题意

\(n\) 天,每天有一个值 \(a_i\)(未确定的值),其中 \(a_i=0\) 的天数称为“休息”,其他的称为“工作”。

若第 \(i\) 天为“工作”,设第 \(i\) 天之前(不包含第 \(i\) 天)连续“工作”的天数为 \(k\),则 \(a_i=\max(0,A-kB)\)\(A,B\) 给定);若第 \(i\) 天为“休息”,则 \(a_i=0\)

现已经确定 \(m\) 天为“休息”,求 \(\sum_{i=1}^{n} a_i\) 的最大值。

\(n,A,B\le 10^9,m\le 10^5\)

题解

\(m\) 天“休息”,相当于把 \(n\) 分成了 \(m+1\) 份,每份是独立的,所以可以分别进行计算。所以,现在不再需要考虑 \(m\) 天“休息”。假设当前子问题的天数为 \(n\)

为了使得和最大,一定是等分更优。例如 \(n=7\),分成 \(3\) 块(即“休息” \(2\) 天),则一定是分别工作 \(1,2,2\) 天。为方便处理,可以将 \(n\)\(1\),钦定最后一天为“休息”。

假设分成 \(x\) 块,设 \(t=\left\lfloor \frac{n}{x}\right\rfloor-1\),则一定是 \(n\bmod x\) 块工作 \(t+1\) 天,\(x-n\bmod x\) 块工作 \(t\) 天。这个可以直接用等差数列求和公式进行计算。

至于如何确定 \(x\),可以发现,答案是一个单峰函数,所以直接三分 \(x\) 即可。

代码

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
#include <cstdio>
#include <cstring>
#include <algorithm>
int n, m, x[100005];
long long A, B, ans;
long long get(int n, int x){ // 计算 n 天分成 x 块的答案
if (x > n) return 0;
int m = n / x - 1;
return ((A + A - (m - 1) * B) * m >> 1) * x + (A - m * B) * (n % x);
}
long long solve(int n){ // 三分求 n 天的答案
int l = 1, r = n, ans = 0;
while (l <= r){
int md = (l + r) >> 1; // 写的有点像二分,其实是三分
if (get(n, md) >= get(n, md + 1)) ans = md, r = md - 1;
else l = md + 1;
}
return get(n, ans);
}
int main(){
scanf("%d%lld%lld%d", &n, &A, &B, &m), x[m + 1] = n + 1;
for (register int i = 1; i <= m; ++i) scanf("%d", x + i);
for (register int i = 0; i <= m; ++i) ans += solve(x[i + 1] - x[i]); // n 在这里已经加 1
printf("%lld\n", ans);
}

评论