HDU 6609 离散化+权值线段树

题意

有一个长度为\(n\)的数组W;

对于每一个\(i\)(\(1<=i<=n\)),你可以选择中任意一些元素W[k] (\(1<=k<i\)),将他们的值改变为0,使得\(\sum_{j=1}^{i-1}W[j] <= m\),

所以输出n个数字,代表对于每一个\(i\),要满足以上条件,至少改变多少个元素.

想法

做这个题的时候,我一直想着怎样贪心,然后一直想不出.

一般来说第一反应应该会是先改变大的元素,但是也可以去考虑保留小的元素.

可以权值线段树+离散化.

离散化之后,用一颗权值线段树维护在前\(i-1\)个元素中,每一种元素出现了多少次.

线段树上每个节点记录每个区间内的元素出现过多少次,和每个区间内所有元素的和.

对于每一个\(i\),我们在前\(i-1\)个元素里面找到尽量多的元素,使它们的和不大于\(m-W[i]\).

在查询的时候,

如果一个节点的和小于\(m-W[i]\),那么答案直接加上这个区间上的元素的个数,即这个区间的元素都保留.

否则如果左子树上的和大于\(m-W[i]\),那么就从左子树中找应该保留哪些数,

如果左子树上的和小于\(m-W[i]\),,那么就保留左子树上的所有元素并且在右子树中找还需要哪些元素,即在右子树中查询不大于\(m-W[i]-\sum左子树上的数\).

代码

#include<stdio.h>
#include<vector>
#include<algorithm>

typedef long long ll;
const int MAXN = 200100;
class Node{
    public:
        int l, r, num;//l,r是区间的范围,num是区间上的数的出现次数
        ll val;//val是区间上的数的和
        Node *lson, *rson;//左右子树
        Node(int _l, int _r);
        int mid();
        int query(int k);
        void update(int pos, int _val);
        void clear();

};
Node::Node(int _l, int _r){//建线段树
    l = _l, r = _r, val = 0, num = 0;
    if(l != r){
        lson = new Node(l, mid());
        rson = new Node(mid()+1, r);
    }
}
void Node::clear(){//删除线段树
    if(l!=r){
        lson->clear();
        rson->clear();
        delete lson;
        delete rson;
    }
}
int Node::mid(){//求l和r的平均值
    return (l+r)>>1;
}
int Node::query(int _val){//查询,在不大于_val的情况下,应该保留哪些数字
    if(val <= _val) return num;//保留整个区间,直接返回num
    if(l == r)  return (_val*num/val);//区间范围为1(即只有一个数)的时候,返回能保留多少个这个数字
    if(lson->val>_val) return lson->query(_val);
    else return lson->num+rson->query(_val-lson->val);//保留左子树上的所有数字,查询右子树
}
void Node::update(int pos, int _val){
    if(l == r){
        val+=_val,num++;
        return;
    }
    if(pos<l || pos>r) return;

    if(pos<=mid()) lson->update(pos, _val);
    else rson->update(pos, _val);
    num = lson->num+rson->num;
    val = lson->val+rson->val;
}
int W[MAXN], uniqueW[MAXN];

int main(){
    int t;
    scanf("%d", &t);
    while(t--){
        int maxW, n, m;
        Node *seqTree = 0;
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i)
            scanf("%d", W+i), uniqueW[i] = W[i];
        std::sort(uniqueW+1, uniqueW+n+1);//离散化
        maxW = std::unique(uniqueW+1, uniqueW+n+1)-(uniqueW+1);
        seqTree = new Node(1, maxW);//建立一颗空的线段树,一开始没有任何的元素
        for(int i = 1; i <= n ; ++i){
            int pos = std::lower_bound(uniqueW+1, uniqueW+maxW+1, W[i])-(uniqueW);//找W[i]离散化之后的位置
            printf("%d ", i-seqTree->query(m-W[i])-1);//query返回的是保留的数,i-query-1就是应该改变的数
            seqTree->update(pos, uniqueW[pos]);//把W[i]插入线段树
        }
        seqTree->clear();//删除一下,以免mle
        printf("\n");
    }
}
上一篇:左偏树


下一篇:String and Times