【题解】放假回家
题目大意
第一行输入 \(n\) 代表有 \(n\) 个同学,\(m\) 代表卡车最多运的物品重量。
第二行输入 \(n\) 个数字 \(a_i\) 表示第 \(i\) 个同学的行李总量。
每组数据为 \(n\) 个子问题:一定将第 \(i\) 个同学的行李放进卡车,而卡车还会有剩余空间,放上第 \(1\) 到 \(i - 1\) 同学的行李,尽量放更多同学的行李,问会有多少同学的行李不能放进卡车。
思路
我们由题意可以看出,每组数据输出的 \(n\) 个答案为独立的数据。但是,第 \(i\) 个问题会包括前面的所有学生,我可以以此将这 \(n\) 个问题联系到一起。
由于我们不知道放了 \(i\) 同学的行李之后,最多能放哪些同学的行李,通过贪心的思想,所以我们应该尽量放重量小的行李,保证等待的同学最少。
由于我们可以放的同学人数是不确定的,我们可以选择二分来获取人数,判断人数为 \(mid\) 时会不会超重。
但每次进行一次排序或优先队列都会超时。
我们可以选择将行李的重量排序,但是我们只能选取前 \(i - 1\) 个同学,但是排了序之后我们并不知道原本前 \(i - 1\) 的排序后的位置,如果我们暴力去找它们的排序后的位置,也一定会超时。
我们可以在二分之前,用 \(index\) 数组记录第 \(i\) 个同学的行李排序后的下标。
我们可以考虑建立两个树状数组 \(c1\) 和 \(c2\) 分别维护人数的前缀和和行李重量的前缀和,将 \(i\) 号同学的信息分别放到 \(c1_{index_i}\) 和 \(c2_{index_i}\)。
所以我们二分的人数,就应该改为树状数组的下标,所以应该初始化 \(l = 1\), \(r = n\)。
可以参考以下代码
#include <cstdio>
#include <algorithm>
#include <cstring>
#define int long long
using namespace std;
const int MAXN = 1e5 + 5;
int T;
int n, m;
struct Node {
int v, id;
bool operator < (const Node o) const { return v < o.v; }
} a[MAXN];
int b[MAXN];
int ind[MAXN];
int c1[MAXN], c2[MAXN];
int lowbit(int x) { return (x & (-x)); }
void update(int* c, int x, int k) {
while (x <= n) {
c[x] += k;
x += lowbit(x);
}
}
int query(int* c, int x) {
int res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
bool check(int sum, int x) {
return sum >= query(c2, x);
}
signed main() {
scanf("%lld", &T);
while (T--) {
memset(c1, 0, sizeof(c1));
memset(c2, 0, sizeof(c2));
scanf("%lld %lld", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i].v), a[i].id = i, b[i] = a[i].v;
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++)
ind[a[i].id] = i;
int ans = 0;
for (int i = 1; i <= n; i++) {
int l = 0, r = n, sum = m - b[i];
while (l + 1 < r) {
int mid = (l + r) >> 1;
if (check(sum, mid))
l = mid;
else
r = mid;
}
int p = check(sum, r) ? r : l;
printf("%lld ", i - query(c1, p) - 1);
update(c1, ind[i], 1), update(c2, ind[i], b[i]);
}
printf("\n");
}
return 0;
}
本文来自博客园,作者:zhou_ziyi,转载请注明原文链接:https://www.cnblogs.com/zhouziyi/p/16485332.html

浙公网安备 33010602011771号