题目来源: 第29次CCF计算机软件能力认证
题目链接: AcWing 5017. 垦田计划
文章目录
一、题目
顿顿总共选中了
n
n
n 块区域准备开垦田地,由于各块区域大小不一,开垦所需时间也不尽相同。
据估算,其中第
i
i
i 块(
1
≤
i
≤
n
1≤i≤n
1≤i≤n)区域的开垦耗时为
t
i
t_i
ti 天。
这
n
n
n 块区域可以同时开垦,所以总耗时
t
T
o
t
a
l
tTotal
tTotal 取决于耗时最长的区域,即:
t
T
o
t
a
l
=
m
a
x
{
t
1
,
t
2
,
…
,
t
n
}
tTotal=max\{t1,t2,…,tn\}
tTotal=max{t1,t2,…,tn}为了加快开垦进度,顿顿准备在部分区域投入额外资源来缩短开垦时间。
具体来说:
- 在第 i i i 块区域每投入 c i c_i ci 单位资源,便可将其开垦耗时缩短 1 天;
- 耗时缩短天数以整数记,即第 i i i 块区域投入资源数量必须是 c i c_i ci 的整数倍;
- 在第 i i i 块区域最多可投入 c i × ( t i − k ) c_i×(t_i−k) ci×(ti−k) 单位资源,将其开垦耗时缩短为 k k k 天;
- 这里的 k k k 表示开垦一块区域的最少天数,满足 0 < k ≤ m i n { t 1 , t 2 , … , t n } 0<k≤min\{t_1,t_2,…,t_n\} 0<k≤min{t1,t2,…,tn};
- 换言之,如果无限制地投入资源,所有区域都可以用 k k k 天完成开垦。
现在顿顿手中共有 m m m 单位资源可供使用,试计算开垦 n n n 块区域最少需要多少天?
输入格式
输入共
n
+
1
n+1
n+1 行。
输入的第一行包含空格分隔的三个正整数
n
n
n,
m
m
m,
k
k
k,分别表示待开垦的区域总数、顿顿手上的资源数量和每块区域的最少开垦天数。
接下来
n
n
n 行,每行包含空格分隔的两个正整数
t
i
t_i
ti 和
c
i
c_i
ci,分别表示第
i
i
i 块区域开垦耗时和将耗时缩短
1
1
1 天所需资源数量。
输出格式
输出一个整数,表示开垦 n n n 块区域的最少耗时。
数据范围
70
%
70\%
70% 的测试数据满足:
0
<
n
0<n
0<n,
t
i
t_i
ti,
c
i
≤
100
c_i≤100
ci≤100 且
0
<
m
≤
1
0
6
0<m≤10^6
0<m≤106;
全部的测试数据满足:
0
<
n
0<n
0<n,
t
i
t_i
ti,
c
i
≤
1
0
5
c_i≤10^5
ci≤105 且
0
<
m
≤
1
0
9
0<m≤10^9
0<m≤109。
输入样例1:
4 9 2
6 1
5 1
6 2
7 1
输出样例1:
5
样例1解释
如下表所示,投入
5
5
5 单位资源即可将总耗时缩短至
5
5
5 天。此时顿顿手中还剩余
4
4
4 单位资源,但无论如何安排,也无法使总耗时进一步缩短。
i i i | 基础耗时 | t i t_i ti 缩减 1 1 1 天所需资源 | c i c_i ci 投入资源数量 | 实际耗时 |
---|---|---|---|---|
1 1 1 | 6 6 6 | 1 1 1 | 1 1 1 | 5 5 5 |
2 2 2 | 5 5 5 | 1 1 1 | 0 0 0 | 5 5 5 |
3 3 3 | 6 6 6 | 2 2 2 | 2 2 2 | 5 5 5 |
4 4 4 | 7 7 7 | 1 1 1 | 2 2 2 | 5 5 5 |
输入样例2:
4 30 2
6 1
5 1
6 2
7 1
输出样例2:
2
样例2解释
投入
20
20
20 单位资源,恰好可将所有区域开垦耗时均缩短为
k
=
2
k=2
k=2 天;受限于
k
k
k,剩余的
10
10
10 单位资源无法使耗时进一步缩短。
二、思路和算法
![](https://s1.ax1x.com/2023/08/27/pPU0By6.png)
由题意可知,开垦
n
n
n 块地所需的最少天数 days
为所有地耗时缩短后的最长时间,且满足
k
≤
d
a
y
s
≤
1
0
5
k≤days≤10^5
k≤days≤105。
以所需的最少天数为分界,可以划分出两个不同的类别。当days
小于最少天数时,其所需资源大于m
;当days
大于等于最少天数时,其所需资源小于等于m
。所以,这个题中的数据具有二段性,可以通过二分的方法解决。
![](https://s1.ax1x.com/2023/08/27/pPU00Qx.png)
其中,每段缩短至x
天所需的资源可以通过如下公式求得:
u
s
e
d
=
(
t
[
i
]
−
x
)
×
c
[
i
]
used = (t[i]-x)×c[i]
used=(t[i]−x)×c[i]
由于题目中所给的参数
n
,
t
i
,
c
i
n,t_i,c_i
n,ti,ci 的最大值都是
1
0
5
10^5
105,因此可能使用的最大资源是
1
0
15
10^{15}
1015,超过了int
的范围,需要使用long long
存储所需的资源数量。
另一种解决方案是,遍历所有地的天数,找到最长的天数,使用已有的资源对其进行”裁剪“,类似于贪心的思想。
总体来看,可以计算出每天所需的资源数目,在缩短天数时,只需将减去要缩短的那天所需的资源即可。对于每天所需资源的计算,可以通过一个差分数组代表其每天所需的资源。对于差分数组s[i]
,表示如果当前最少天数为i
天,将其缩短为i-1
天所需的资源数量。
题目要求输入两个正整数,分别代表开垦一块地的时间(设为
t
t
t)和缩短一天所需要的资源数目(设为
c
c
c)。那么,对于每次的输入,可以通过公式s[1]+c, s[t+1]-c
初始化差分数组,然后求其前缀和(for
循环中使用s[i] += s[i-1]
)即可得到所需的差分数组。
最后,从可能的最大天数开始遍历差分数组,缩短逐个缩短天数即可。
三、代码及复杂度
方法一:二分 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m, k;
int t[N], c[N];
bool check(int mid)
{
LL sum = 0;
for (int i = 0; i < n; i++)
if (t[i] > mid)
sum += (LL)(t[i] - mid) * c[i];
return sum <= m;
}
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < n; i++) scanf("%d%d", &t[i], &c[i]);
int l = k, r = N;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // 如果mid可以满足,那么所有大于等于mid的都可以满足 答案->[l, mid]
else l = mid + 1; // 如果mid不能满足, 那么答案在区间[mid+1, r]中
}
printf("%d\n", l);
return 0;
}
方法二:差分 O ( n ) O(n) O(n)
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m, k;
LL s[N];
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 0; i < n; i++)
{
int t, c;
scanf("%d%d", &t, &c);
s[1] += c, s[t + 1] -= c;
}
for (int i = 0; i < N; i++) s[i] += s[i - 1];
LL cost = 0;
int i = N - 1;
while (i > k && cost + s[i] <= m) cost += s[i--];
printf("%d\n", i);
return 0;
}
复杂度分析
对于二分的方法,包括二分和枚举两部分。其中,二分答案部分的时间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)。在每次二分结束后,都需要对二分的答案进行枚举计算,判断是否符合题意,该部分时间复杂度为 O ( n ) O(n) O(n)。故二分方法的时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
对于差分的方法,包括初始化和遍历两部分,且这两部分是并列关系。在初始化时,需要输入数据和计算前缀和,其时间复杂度为 O ( n ) O(n) O(n);在遍历阶段,从大到小一次遍历差分数组,其时间复杂度为 O ( n ) O(n) O(n)。综上,差分方法的时间复杂度为 O ( n ) O(n) O(n)。