• 2019 Multi-University Training Contest 3


    Find the answer 权值线段树

    题意:给n个数字,m值,输出n个值,每次问对于前缀和1~i(1<=i<=n) 中,最少去掉(1,i-1)中多少个数字才能使前缀和小于m;

    思路:很多做法,可以使用权值线段树,用权值线段树记录数组的前缀和和前缀和所对应得数字个数,因为权值线段树中叶子节点记录的数字是从小到大的,所以我们可以求出权值线段树前缀和小于一个值所能最多添加多少个数字(优先取左子树的数字),

    也就是每次求最多有多少个数字的前缀和小于m-a[i]求出ans,然后i-1-ans就是所需减掉的最小个数

    #include <bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int maxn=2e5+5;
    ll sum[maxn*4];
    int num[maxn*4]; // sum记录前缀和 num记录个数
    int a[maxn];
    int b[maxn];
    void build(int root,int l,int r)
    {
        sum[root]=0;
        num[root]=0;
        if(l==r)
            return;
        int mid=(l+r)/2;
        build(root<<1,l,mid);
        build(root<<1|1,mid+1,r);
    }
    void update(int root,int l,int r,int index)
    {
        if(l==r)
        {
            sum[root]+=b[l];
            num[root]++;
            return;
        }
        int mid=(l+r)/2;
        if(index<=mid)
            update(root<<1,l,mid,index);
        else
            update(root<<1|1,mid+1,r,index);
        sum[root]=sum[root<<1]+sum[root<<1|1];
        num[root]=num[root<<1]+num[root<<1|1];
    }
    int query(int root,int l,int r,int k) // 查询最多有多少个数字和<=k 
    {
        if(l==r)
        {
    //        return k/b[l];
            if(k == 0) return 0;
            else if(k >= b[l])
            {
                return k / b[l];
            }
            return 0;
        }
        int mid=(l+r)/2;
        ll Lsum=sum[root<<1];
        if(Lsum>=k) return query(root<<1,l,mid,k);
        return num[root<<1]+query(root<<1|1,mid+1,r,k-Lsum);
    }
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            ll n,m;
            scanf("%lld%lld",&n,&m);
            for(int i = 1; i <= n; ++i)
            {
                scanf("%d",&a[i]);
                b[i]=a[i];
            }
            sort(b+1,b+1+n);
            int cnt=unique(b+1,b+1+n)-b-1;
            build(1,1,n);
            ll ret=0;
            for(int i=1; i<=n; i++)
            {
                ret+=a[i];
                if(ret<=m)
                    printf("0 ");
                else
                {
                    ll tmp=m-a[i];
                    int ans=query(1,1,n,tmp);
                    printf("%d ",i-1-ans);
                }
                int pos=lower_bound(b+1,b+1+cnt,a[i])-b;
                update(1,1,n,pos);
            }
            printf("
    ");
        }
        return 0;
    }
    View Code

    做法2 树状数组+二分

    思路:对n个数字离散化,注意这里不能去重,之后用两个树状数组,一个维护目前的前缀和,一个维护前缀和对应得数字个数,一次查询复杂度为log n,

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn=2e5+10;
    #define ll long long
    struct note
    {
        int id,num;
    } b[maxn];
    ll sum[maxn],num[maxn];
    int n,m;
    int a[maxn];
    int pos[maxn];
    ll asksum(int x)
    {
        ll ans=0;
        for(; x; x-=x&-x)
            ans+=sum[x];
        return ans;
    }
    void addsum(int x,int y)
    {
        for(; x<=n; x+=x&-x) sum[x]+=y;
    }
    ll asknum(int x)
    {
        ll ans=0;
        for(; x; x-=x&-x)
            ans+=num[x];
        return ans;
    }
    void addnum(int x,int y)
    {
        for(; x<=n; x+=x&-x) num[x]+=y;
    }
    int cmp(note a,note b)
    {
        return a.num<b.num;
    }
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&m);
            for(int i=1; i<=n; i++)
                sum[i]=num[i]=0;
            for(int i=1; i<=n; i++)
            {
                scanf("%d",&a[i]);
                b[i].id=i;
                b[i].num=a[i];
            }
            sort(b+1,b+1+n,cmp);
            for(int i=1; i<=n; i++)
                pos[b[i].id]=i;
            ll ret=0;
            for(int i=1; i<=n; i++)
            {
                ret+=a[i];
                if(ret<=m)
                    printf("0 ");
                else
                {
                    ll temp=m-a[i];
                    int l,r,mid;
                    l=0,r=n+1;
                    while(l<r)
                    {
                        mid=(l+r+1)/2;
                        if(asksum(mid)<=temp)
                            l=mid;
                        else
                            r=mid-1;
                    }
                    printf("%d ",i-1-asknum(l));
                }
                addnum(pos[i],1);
                addsum(pos[i],a[i]);
            }
            printf("
    ");
        }
    }
    View Code

    Distribution of books

    题意:给n个数字,允许去掉结尾的部分,问把这n个数字分成k的区间的最大区间和最小值。

    思路:如果没有允许去掉结尾部分,就是一个很简单的二分贪心,而加了这个条件之后问题不再满足贪心,需要dp解决,

    设f[i]表示第i个数字满足划分到的最大的块,则转移方程为f[i]=maxf[j](1<=j<i&&sum[j]-sum[i]<=mid)+1,而对于[1,i)这个区间没有必要每次都遍历一遍,可以使用线段树,下标sum[i],值为f[i],这样就可以每次询问区间了,然后修改其中一个位置,把f[i]放到了线段树上。

    #include<bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    const int maxn=2e5+10;
    const ll INF=1e18;
    struct note
    {
        int left,right,maxx;
    } tree[maxn*4];
    ll a[maxn];
    ll b[maxn],cntb;
    int n,k;
    void pushup(int id)
    {
        tree[id].maxx=max(tree[id<<1].maxx,tree[id<<1|1].maxx);
    }
    
    void build(int id,int l,int r)
    {
        tree[id].left=l;
        tree[id].right=r;
        if(l==r)
            tree[id].maxx=-0x3f3f3f3f;
        else
        {
            int mid=(l+r)/2;
            build(id<<1,l,mid);
            build(id<<1|1,mid+1,r);
            pushup(id);
        }
    }
    
    void update(int id,int pos,int val)
    {
        if(tree[id].left==tree[id].right)
            tree[id].maxx=val;
        else
        {
            int mid=(tree[id].left+tree[id].right)/2;
            if(pos<=mid) update(id<<1,pos,val);
            else update(id<<1|1,pos,val);
            pushup(id);
        }
    }
    
    int query(int id,int l,int r)
    {
        if(l<=tree[id].left&&tree[id].right<=r)
            return tree[id].maxx;
        int ans=-0x3f3f3f3f;
        int mid=(tree[id].left+tree[id].right)/2;
        if(l<=mid) ans=max(ans,query(id<<1,l,r));
        if(r>mid) ans=max(ans,query(id<<1|1,l,r));
        return ans;
    }
    
    int check(ll mid)
    {
    //    if(mid<a[1])
    //        return 0;
        build(1,1,cntb);
        int pos=lower_bound(b+1,b+1+cntb,0)-b;
        update(1,pos,0);
    //    memset(f,0,sizeof(f));
        for(int i=1; i<=n; i++)
        {
            int pos1=lower_bound(b+1,b+1+cntb,a[i]-mid)-b;
            int pos2=lower_bound(b+1,b+1+cntb,a[i])-b;
            if(pos1>cntb) continue;
            int maxx=query(1,pos1,cntb);
            if(maxx+1>0)
                update(1,pos2,maxx+1);
            if(maxx+1>=k)
                return 1;
        }
        return 0;
    }
    
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d",&n,&k);
            for(int i=1; i<=n; i++)
            {
                ll x;
                scanf("%lld",&x);
                a[i]=a[i-1]+x;
                b[i]=a[i];
            }
            b[n+1]=0;
            sort(b+1,b+2+n);
            cntb=unique(b+1,b+2+n)-b-1;
            ll l=-INF,r=INF,ans;
            while(l<=r)
            {
                ll mid=(l+r)/2;
                if(check(mid))
                {
                    ans=mid;
                    r=mid-1;
                }
                else
                    l=mid+1;
            }
            printf("%lld
    ",ans);
        }
    }
    View Code
  • 相关阅读:
    由12306.cn谈谈网站性能技术 岁月无情
    雅虎网站页面性能优化的34条黄金守则 岁月无情
    [纯技术讨论]从12306谈海量事务高速处理系统 岁月无情
    解密淘宝网的开源架构(转) 岁月无情
    HttpCombiner.ashx处理 岁月无情
    转 自定义控件属性的特性大全 岁月无情
    模式窗口window.showModal 岁月无情
    动态加载JSashx的妙用 岁月无情
    ASP.NET中Get和Post的用法 岁月无情
    初学Oracle的笔记(1)——基础内容(实时更新中..)
  • 原文地址:https://www.cnblogs.com/dongdong25800/p/11608495.html
Copyright © 2020-2023  润新知