2019 Multi-University Training Contest 3. 1006.Find the answer

Find the Answer

Problem Description

Given a sequence of n integers called W and an integer m. For each i (1 <= i <= n), you can choose some elements Wk (1 <= k < i), and change them to zero to make ∑ij=1Wj<=m. So what’s the minimum number of chosen elements to meet the requirements above?.

Input

The first line contains an integer Q — the number of test cases.
For each test case:
The first line contains two integers n and m — n represents the number of elemens in sequence W and m is as described above.
The second line contains n integers, which means the sequence W.
1 <= Q <= 15
1 <= n <= 2*105
1 <= m <= 109
For each i, 1 <= Wi <= m

Output

For each test case, you should output n integers in one line: i-th integer means the minimum number of chosen elements Wk (1 <= k < i), and change them to zero to make ∑ij=1Wj<=m.

Sample Input

2
7 15
1 2 3 4 5 6 7
5 100
80 40 40 40 60

Sample

0 0 0 0 0 2 3
0 1 1 2 3

Brief Description

给定n个数,和一个数字m
为了使前i个数(包括wi)的和小于等于m,我们可以将前i个数(不包括wi)的若干个置0。
问对于n各数中的每个数wi,至少置0多少个数才能满足上述条件

样例分析

5 100
80 40 40 40 60

对于第一个80, 小于m(100) 因此是0个
第二个40,80+40 > m,不满足条件,因此必须在前面删除一些,因为只有一个,所以最少删除一个,输出1
第三个40,80+40+40=160>m,同理,必须在前面删除一些,如果删除的是40,那么剩下的80+40仍然大于100,仍需多删一个,为了删除的最少,只删除80一个, 故输出1
同理。。。。。。
最终输出 0 1 1 2 3

Solution

很容易想到,要删除的少,肯定要先删除数值大的
问题即转化为 求一个k,使得删除前k大后,剩余的和<=m

依题意,wi(当前值)是不可以删的,只可以删除前面的
问题又可以转化为求最小的temp个数,使得他们的和+wi<=m

那么最终k就等于 i-temp

我们的目标就放在了求temp值
怎么求呢?可以尝试二分搜索temp值
对于当前搜索值curr,如果前curr小个数的和+w[i]<=m,即满足条件

Example

例如下图:当前处理的为黄色4=6
后面灰色的暂未处理,蓝色的为已经处理

可以轻易看出 最多 前2小 + 6(即:1+4+6 <=m)

因此答案为 3-2 = 1 (最少删除1个【5】)
在这里插入图片描述

如何编程实现此思路

接下来,我们的目标又转移到求区间前curr小个数的和。

如果暴力求和,肯定是会TLE的。因此可以想到用某种数据结构。
树状数组或线段树。

先将数据离散化,
例如 80 40 40 40 60 离散成 5 1 2 3 4
用两个树状数组 一个维护当前已有的i个数的区间和 ,一个维护区间的数的个数

这两个树状数组访问举例说明:
当处理到第三个数(2)时, 对区间【1,4】求和表示: 第一小+到第4小 但是由于当前已有数只有 5 1 2, 没有第3小和第四小,所以求和结果为 1+2=3
另一个树状数组区间中数的个数,比如访问区间【1,4】 得到2

**树状数组在这道题上的运用 **
例如右图:当前处理的为黄色4=6
后面灰色的暂未处理,蓝色的为已经处理
对第一个树状数组,最多访问 query【1,4】 + 6 <=m

对于刚刚得到的最大区间,访问第二个树状数组【1,4】 得知【1,4】有2个
故输出 3-2=1 (3个蓝色;最多可前2小求和)

Data Structure

在这里插入图片描述

AC Code:

/*
 * Copyright (c) 2019 Ng Kimbing, HNU, All rights reserved. May not be used, modified, or copied without permission.
 * @Author: Ng Kimbing, HNU.
 * @LastModified:2019-07-29 T 23:00:44.448 +08:00
 */

package ACMProblems.DataStructureProblem.TreeArray;

import java.util.Arrays;
import java.util.Comparator;

import static ACMProblems.ACMIO.*;

public class FindTheAnswer {
    private static final int maxN = 200005;
    private static Integer[] data = new Integer[maxN];
    private static int[] w = new int[maxN];
    private static int[] indexOfV = new int[maxN];
    private static int[] ans = new int[maxN];
    private static int n;

    private static void discrete(int n) {
        for (int i = 1; i <= n; i++)
            data[i] = i;
        Arrays.sort(data, 1, n + 1, Comparator.comparingInt(o -> w[o]));
        for (int i = 1; i <= n; i++)
            indexOfV[data[i]] = i;
    }

    static class TreeArray {
        long[] treeArray;

        TreeArray() {
            treeArray = new long[maxN];
        }

        private int lowBit(int x) {
            return x & (-x);
        }

        private void add(int treeIndex, int value) {
            while (treeIndex <= n) {
                treeArray[treeIndex] += value;
                treeIndex += lowBit(treeIndex);
            }
        }

        private long sum(int x) {
            long ans = 0;
            while (x != 0) {
                ans += treeArray[x];
                x -= lowBit(x);
            }
            return ans;
        }

        private long query(int l, int r) {
            return sum(r) - sum(l - 1);
        }

        private void clear() {
            Arrays.fill(treeArray, 0);
        }
    }

    public static void main(String[] args) throws Exception {
        int caseNum;
        TreeArray sumTree = new TreeArray();
        TreeArray countTree = new TreeArray();
        for (caseNum = nextInt(); caseNum-- != 0; ) {
            sumTree.clear();
            countTree.clear();
            n = nextInt();
            int m = nextInt();
            for (int i = 1; i <= n; ++i) {
                w[i] = nextInt();
                indexOfV[i] = w[i];
            }
            discrete(n);
            ans[1] = 0;
            sumTree.add(indexOfV[1], w[1]);
            countTree.add(indexOfV[1], 1);
            for (int i = 2; i <= n; i++) {
                int l = 1, r = n;
                //找到尽可能多的前r小的数,使得v[i] + sum(1, r) <= m
                while (l <= r) {
                    int mid = (l + r) >> 1;
                    if (sumTree.query(1, mid) + w[i] <= m)
                        l = mid + 1;
                    else r = mid - 1;
                }
                //countTree.query(1,r)代表前r小的数在当前区间有多少个
                ans[i] = (int) (i - 1 - countTree.query(1, r));
                sumTree.add(indexOfV[i], w[i]);
                countTree.add(indexOfV[i], 1);
            }
            for (int i = 1; i <= n; ++i)
                out.print(ans[i] + " ");
            out.println();
        }
        out.flush();
    }

}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值