2 条题解
-
1
题目描述:
有两个序列 a1, a2, ..., an 和 b1, b2, ..., bm,以及一个总预算 k。
你可以按顺序从两个序列的开头取数(每次只能取当前序列的第一个元素),取走的数会累加其值到总花费中。
问在总花费不超过 k 的前提下,最多能取多少个数。
一个容易想到的贪心策略是:
用两个指针 i 和 j 分别指向 a 和 b 的当前开头。
每次比较 a[i] 和 b[j],如果 a[i] > b[j] 就取 b[j](因为代价更小),否则取 a[i]。
这种贪心在某些情况下会出错。
例如:
n=4, m=3, k=6
a = [1, 4, 1, 1]
b = [3, 3, 5]
贪心过程:先取 a[1]=1,再取 b[1]=3,总花费 1+3=4,此时 a 指针指向 4,b 指针指向 3。下一步无论取 a[2]=4 还是 b[2]=3 都会使花费超过 k,最终只能取 2 个数。
但事实上,我们完全可以跳过 b 而全部取 a:取 a[1]=1,a[2]=4,a[3]=1,总花费 1+4+1=6,可以取 3 个数。
贪心漏掉了这种情况,因此不能直接贪心。
正确解法:枚举 + 二分
由于两个序列都是按顺序取,如果我们决定取 x 个数,那么必然是从 a 中取 i 个,从 b 中取 x-i 个,并且 0 <= i <= n,0 <= x-i <= m。
我们可以预处理 a 和 b 的前缀和:
sa[i] = a1 + a2 + ... + ai
sb[j] = b1 + b2 + ... + bj
那么取 i 个 a 和 j 个 b 的总花费为 sa[i] + sb[j]。
对于给定的 x,我们只需检查是否存在 i 属于 [0, x] 满足 i <= n 且 x-i <= m,且 sa[i] + sb[x-i] <= k。
如果存在,说明 x 是可行的。
由于 x 越大越难满足,我们可以对 x 进行二分查找,找到最大的可行 x。
代码实现(C++):
#include<bits/stdc++.h> using namespace std; #define int long long int n, m, k; int a[200100], b[200100], sa[200100], sb[200100]; bool check(int x) { // 枚举从 a 中取 i 个,从 b 中取 x-i 个 for (int i = 0; i <= x; i++) { if (i > n || x - i > m) continue; // 越界跳过 if (sa[i] + sb[x - i] <= k) return true; } return false; } signed main() { cin >> n >> m >> k; for (int i = 1; i <= n; i++) { cin >> a[i]; sa[i] = sa[i - 1] + a[i]; } for (int j = 1; j <= m; j++) { cin >> b[j]; sb[j] = sb[j - 1] + b[j]; } int l = -1, r = n + m + 1, mid; while (l + 1 < r) { mid = (l + r) / 2; if (check(mid)) l = mid; else r = mid; } cout << l << endl; return 0; }时间复杂度:
总复杂度 O((n+m) log(n+m)),可以通过本题。
总结:
直接贪心(每次取较小值)是错误的,因为序列并非有序,可能为了取后面的较小值而放弃前面的大值反而更优。
正确做法是枚举从两个序列中各取多少个数,利用前缀和快速计算总花费,并二分答案找到最大可行数量。
这种“枚举分配”的思路在类似“从两个序列开头按顺序取数”的问题中非常常用。
-
0
import java.util.Scanner; public class Main { public static void main(String[] args) {
Scanner sc = new Scanner(System.in); int n=sc.nextInt(); int m=sc.nextInt(); long k=sc.nextLong(); int[]a=new int[n+2]; int[]b=new int[m+2]; long[]sumA=new long[n+2]; long[]sumB=new long[m+2]; for(int i=1;i<=n;i++) {//前缀和 a[i]=sc.nextInt(); sumA[i]=a[i]+sumA[i-1]; } for(int i=1;i<=m;i++) { b[i]=sc.nextInt(); sumB[i]=b[i]+sumB[i-1]; } int max=0; for(int i=0;i<=n;i++) {//遍历sumA if(sumA[i]>k)break; int l=0,r=m,j=0; while(l<=r) {//二分找出当前最大sumB int mid=l+(r-l)/2; if(sumA[i]+sumB[l]<=k) { l=mid+1; j=mid; }else { r=mid-1; } } max=Math.max(max, i+j); } System.out.println(max); }}
- 1
信息
- ID
- 385
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 7
- 标签
- 递交数
- 438
- 已通过
- 91
- 上传者