【题解】垦田计划

题目来源: 第29次CCF计算机软件能力认证
题目链接: AcWing 5017. 垦田计划

一、题目

顿顿总共选中了 n n n 块区域准备开垦田地,由于各块区域大小不一,开垦所需时间也不尽相同。
据估算,其中第 i i i 块( 1 ≤ i ≤ n 1≤i≤n 1in)区域的开垦耗时为 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×(tik) 单位资源,将其开垦耗时缩短为 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<kmin{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 ci100 0 < m ≤ 1 0 6 0<m≤10^6 0<m106
全部的测试数据满足: 0 < n 0<n 0<n, t i t_i ti, c i ≤ 1 0 5 c_i≤10^5 ci105 0 < m ≤ 1 0 9 0<m≤10^9 0<m109

输入样例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 单位资源无法使耗时进一步缩短。

二、思路和算法

由题意可知,开垦 n n n 块地所需的最少天数 days为所有地耗时缩短后的最长时间,且满足 k ≤ d a y s ≤ 1 0 5 k≤days≤10^5 kdays105

以所需的最少天数为分界,可以划分出两个不同的类别。当days小于最少天数时,其所需资源大于m;当days大于等于最少天数时,其所需资源小于等于m。所以,这个题中的数据具有二段性,可以通过二分的方法解决。

其中,每段缩短至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)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张鱼·小丸子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值