• hdu多校第三场 1007 (hdu6609) Find the answer 线段树


    题意:

    给定一组数,共n个,第i次把第i个数扔进来,要求你删掉前i-1个数中的一些(不许删掉刚加进来这个数),使得前i个数相加的和小于m。问你对于每个i,最少需要删掉几个数字。

    题解:

    肯定是优先删大数,一开始想的方法类似于尺取,就是维护一个大顶堆作为现有的数,小顶堆作为要删的数,每次大顶堆的元素总和大于m了,就把堆顶扔进小顶堆,扔完了,再把小顶堆的堆顶扔回大顶堆,在会导致大顶堆值总和大于m时停止。

    但是很容易会被1 1 1 1 1 1 1 1 1 1 100 这样的数据卡掉。

    然后想到了,维护一个线段树,假想线段树根节点从左到右保存原数列中从大到小的数,但是一开始置为虚节点,值为0,而每当计算到数列的某一位时,再把这一位在线段树上该在的位置赋上实值,这个过程需要离线排序,nlogn。

    二分,每次枚举节点k,在线段树上求区间和,使得1到k上,所有非虚节点和大于等于sum-m,找到最小k,然后输出1-k的非虚节点的数量,二分复杂度logn,线段树上求区间和复杂度logn。时间复杂度$O(nlog^2n)$

    这个想法已经很接近正确答案了,还是T了,实际上,线段树本身就是一个二分的结构,又为什么要在线段树上二分枚举节点呢?归根结底还是我对于常用数据结构的不熟悉,只会套用板子,正解是直接在线段树上二分求前缀和,总时间复杂度$O(nlogn)$

    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<cassert>
    #define MAXN 200005
    #define LL long long
    using namespace std;
    struct Node{
        int l,r;
        LL val;
        int exis;
    }node[MAXN*4];
    void build(int l,int r,int x){
        node[x].l=l;
        node[x].r=r;
        if(l==r){
            node[x].exis=0;
            node[x].val=0;
            return ;
        }else{
            int mid=(l+r)/2;
            build(l,mid,x*2);
            build(mid+1,r,x*2+1);
        }
        node[x].val=node[2*x].val+node[2*x+1].val;
        node[x].exis=node[2*x].exis+node[2*x+1].exis;
        return ;
    }
    LL query(int l,int r,int x){
        if(l<=node[x].l && node[x].r<=r)return node[x].exis;
        if(node[x].r<l || r<node[x].l)return 0;
        LL ans=0;
        if(l<=node[x*2].r)ans+=query(l,r,2*x);
        if(node[x*2+1].l<=r)ans+=query(l,r,2*x+1);
        return ans;
    }
    void add(int id,int num,int x){
        if(node[x].l==node[x].r){
            node[x].val=num;
            node[x].exis=1;
            return ;
        }
        if(id<=node[x*2].r){
            add(id,num,x*2);
        }else{
            add(id,num,x*2+1);
        }
        node[x].val=node[x*2].val+node[x*2+1].val;
        node[x].exis=node[x*2].exis+node[x*2+1].exis;
        return ;
    }
    int bsearch(LL last,int x){
        if(node[x].l==node[x].r)return node[x].exis;
        if(node[x].val==last)return node[x].exis;
        if(node[x].val>last){
            if(last<=node[x*2].val)return bsearch(last,x*2);
            else return node[x*2].exis+bsearch(last-node[x*2].val,x*2+1);
        }
    }
    struct Tag{
        int index;
        int val;
        int rank;
    }tag[MAXN];
    inline bool cmpval(const Tag &a,const Tag &b){
        return a.val>b.val;
    }
    inline bool cmpindex(const Tag &a,const Tag &b){
        return a.index<b.index;
    }
    int main(){
        int q;
        scanf("%d",&q);
        while(q--){
            int n;
            LL m;
            scanf("%d %lld",&n,&m);
    //        memset(node,0,sizeof node);
            build(1,n,1);
            for(int i=1;i<=n;i++){
                scanf("%d",&tag[i].val);
                tag[i].index=i;
            }
            sort(tag+1,tag+1+n,cmpval);
            for(int i=1;i<=n;i++){
                tag[i].rank=i;
            }
            sort(tag+1,tag+1+n,cmpindex);
            LL sum=0;
            for(int i=1;i<=n;i++){
    //            printf("%d %d %d
    ",tag[i].index,tag[i].val,tag[i].rank);
                sum+=tag[i].val;
                if(sum<=m)printf("0 ");
                else printf("%d ",bsearch(sum-m,1));
                add(tag[i].rank,tag[i].val,1);
    //            printf("
    ");
    //            for(int j=1;j<=4*n;j++){
    //                printf("%d %d %lld %d
    ",node[j].l,node[j].r,node[j].val,node[j].exis);
    //            }
    //            printf("
    ");
            }
            printf("
    ");
    //        for(int j=1;j<=4*n;j++){
    //            printf("%d %d %lld %d
    ",node[j].l,node[j].r,node[j].val,node[j].exis);
    //        }
    //        printf("
    ");
        }
        return 0; 
    } 

    后记:

    在群里看到了一个神仙做法。

    这道题难点在于刚加进去的点,不能立即删,如果可以立即删,那么,后一个数字加进来,删哪些,一定可以通过前一个数字加进来后删哪些快速求出,贪心删去最大的就行了。

    神仙的思路是,假想这个游戏允许删掉刚刚加进来的那个数字,那么,记tot[i]为第i个数字加进来后,应该删去哪些,tot[i]肯定是tot[i+1]的子集。

    而实际上,第i个数字加进来后,只能删前i-1个数字,那么,临时删去一些数字,用num[i]记录临时删掉的数字,那么,此时的答案就是tot[i-1]+num[i].

    贪心删去最大的就行,因此可以用multiset维护。

    神仙的链接:https://blog.csdn.net/qq_41603898/article/details/97674737

  • 相关阅读:
    3、UML中的类图及类图之间的关系
    2、GoF的23种设计模式
    1、软件设计模式概念
    枚举
    泛型
    MySQL
    蚁群算法MATLAB解VRP问题
    蚁群算法MATLAB解TSP问题
    模拟退火解TSP问题MATLAB代码
    模拟退火学习
  • 原文地址:https://www.cnblogs.com/isakovsky/p/11273411.html
Copyright © 2020-2023  润新知