题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=6609
题意:
给你一串N个数的序列
W
W
W和一个数M,对于每一个
i
i
i,你可以选择令一些
W
k
=
0
(
k
∈
[
1
,
i
−
1
]
)
W_k = 0 (k ∈[1,i - 1])
Wk=0(k∈[1,i−1]),即除了当前位之外都能选,题目求对每一位
i
i
i,求最少选多少数使得
∑
j
=
1
i
W
i
≤
M
\displaystyle\sum_{j=1}^iW_i\leq M
j=1∑iWi≤M。
题解:
需要注意的是,这里的每一位
i
i
i都是独立的,跟前面的
i
i
i是否满足无关。这里的最优策略显然是从大到小一直选下去,直到剩下数的和已经小于等于
M
M
M。因为每一位
i
i
i都是独立的,所以要遍历整个W来求,根据题目的数据,单次的处理复杂度要是
l
o
g
N
logN
logN级别的才不至于超时。这里用线段树来维护一个区间前N大和,详细见代码。
代码:
#include<bits/stdc++.h>
#define lc 2*u
#define rc 2*u+1
#define mid (l+r)/2
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 10;
struct node {
int n, p, p2;//数值,原来的位置,根据数值排序的位置
}a[MAX];
bool cmp1(const node& a, const node& b) {
return a.n > b.n;
}
bool cmp2(const node& a, const node& b) {
return a.p < b.p;
}
struct SegmentTreeNode {
int sz, l, r;//当前区间数的个数,区间为[l, r]
ll w;//区间数和
}t[4 * MAX];
int N, cnt[MAX];//cnt[i]记录第i位答案
ll M, pre[MAX];//pre[i]记录前缀和
void build(int u, int l, int r) {//建树
t[u].l = l, t[u].r = r;
if (l == r) {
t[u].w = a[l].n;
t[u].sz = 1;
return;
}
build(lc, l, mid);
build(rc, mid + 1, r);
t[u].w = t[lc].w + t[rc].w;
t[u].sz = t[lc].sz + t[rc].sz;
}
void remove(int u, int p) {//删除第p大的数
if (t[u].l == p && t[u].sz == 1) {
t[u].w = 0;
t[u].sz = 0;
return;
}
int m = (t[u].l + t[u].r) / 2;
if (p <= m)remove(lc, p);
else remove(rc, p);
t[u].w = t[lc].w + t[rc].w;
t[u].sz = t[lc].sz + t[rc].sz;
}
int query(int u, ll last) {//查询需要删掉几个数
if (last <= 0)return 0;
if (t[u].sz == 1)return 1;
if (t[lc].w >= last)return query(lc, last);//如果当前仍需要删掉的数 小于等于 左子树的区间和,就进左子树
else return t[lc].sz + query(rc, last - t[lc].w);//反之,左子树数已经不够了,先加上左子树的数个数,再进右子树
}
int main() {
#ifdef ACM_LOCAL
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
ios::sync_with_stdio(0);
int Q;
cin >> Q;
while (Q--) {
cin >> N >> M;
for (int i = 1; i <= N; i++)
cin >> a[i].n, a[i].p = i, pre[i] = pre[i - 1] + a[i].n;
sort(a + 1, a + 1 + N, cmp1);//从大到小排序
for (int i = 1; i <= N; i++)a[i].p2 = i;//记录排序后的位置,之后删除操作要用到
build(1, 1, N);//建树
sort(a + 1, a + 1 + N, cmp2);//还原原来的序列
for (int i = N; i >= 1; i--) {//一定要从后往前遍历,因为后面的数删掉不会对前面有影响,反过来就有影响了
remove(1, a[i].p2);//先删掉这个数,因为这个数是不能去掉的
cnt[i] = query(1, pre[i] - M);//寻找答案
}
for (int i = 1; i <= N; i++)
cout << cnt[i] << " ";
cout << endl;
}
return 0;
}