JZOJ 5431. 【NOIP2017提高A组集训10.28】序列操作

Description

一开始有n个非负整数hi,接下来会进行m次操作,第i次操作给出一个数c[i],要求你选出c[i]个大于零的数并将它们减去1。
问最多可以进行多少轮操作后无法操作(即没有c[i]个大于零的数)

Input

第一行两个数表示n和m
第二行n个数描述h[i]
第三行m个数描述c[i]

Output

一行表示答案,即最多可以进行多少轮操作后无法操作

Sample Input

输入1:

3 5
1 2 5
1 2 3 2 1

输入2:

5 5
1 3 3 4 5
1 2 4 4 4

Sample Output

输出1:

3

输出2:

5

Data Constraint

对于10%的数据满足,1<=n,m<=5
对于另外20%的数据满足,1<=n<=8,1<=h[i]<=7
对于50%的数据满足,1<=n,m<=1000
对于80%的数据满足,1<=n,m<=100000
对于100%的数据满足,1<=n,m<=1000000

Solution

  • 虽然数据有点大,还是决定线段树……

  • 显然,选择的 c 个数一定选最大的那 c 个。

  • 为了方便处理,先把数组排序,中途维护时也保证单调。

  • 区间减,如果第 nc+1 个数属于一段相同的数,就把相同的那段移到前面减(保证单调)。

  • 时间复杂度 O(N log N)

Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e6+2;
struct data
{
    int mx,c;
}f[N<<2];
int ans;
int a[N];
inline int read()
{
    int X=0,w=1; char ch=0;
    while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
    return X*w;
}
inline int max(int x,int y)
{
    return x>y?x:y;
}
inline void modify(int v)
{
    if(f[v].c)
    {
        int ls=v<<1,rs=ls|1;
        f[ls].c+=f[v].c;
        f[rs].c+=f[v].c;
        f[ls].mx-=f[v].c;
        f[rs].mx-=f[v].c;
        f[v].c=0;
    }
}
inline void make(int v,int l,int r)
{
    if(l==r)
    {
        f[v].mx=a[l];
        return;
    }
    int mid=(l+r)>>1;
    make(v<<1,l,mid);
    make(v<<1|1,mid+1,r);
    f[v].mx=max(f[v<<1].mx,f[v<<1|1].mx);
}
inline void change(int v,int l,int r,int x,int y)
{
    if(l==x && r==y)
    {
        f[v].c++;
        f[v].mx--;
        return;
    }
    modify(v);
    int mid=(l+r)>>1;
    if(y<=mid) change(v<<1,l,mid,x,y); else
        if(x>mid) change(v<<1|1,mid+1,r,x,y); else
        {
            change(v<<1,l,mid,x,mid);
            change(v<<1|1,mid+1,r,mid+1,y);
        }
    f[v].mx=max(f[v<<1].mx,f[v<<1|1].mx);
}
inline int findnum(int v,int l,int r,int x)
{
    modify(v);
    if(l==r) return f[v].mx;
    int mid=(l+r)>>1;
    int t=x<=mid?findnum(v<<1,l,mid,x):findnum(v<<1|1,mid+1,r,x);
    f[v].mx=max(f[v<<1].mx,f[v<<1|1].mx);
    return t;
}
inline int findpos(int v,int l,int r,int x)
{
    modify(v);
    if(l==r) return l;
    int mid=(l+r)>>1;
    int t=f[v<<1].mx>=x?findpos(v<<1,l,mid,x):findpos(v<<1|1,mid+1,r,x);
    f[v].mx=max(f[v<<1].mx,f[v<<1|1].mx);
    return t;
}
int main()
{
    int n=read(),m=read();
    for(int i=1;i<=n;i++) a[i]=read();
    sort(a+1,a+1+n);
    make(1,1,n);
    for(ans=0;ans<m;ans++)
    {
        int x=read(),t=findnum(1,1,n,n-x+1);
        if(t<=0) break;
        int pos2=findpos(1,1,n,t);
        if(pos2<n-x+1)
        {
            int pos1=findpos(1,1,n,t+1);
            if(pos1<=n && findnum(1,1,n,pos1)<=t) pos1++;
            if(pos1<=n) change(1,1,n,pos1,n);
            if(pos2+pos1-(n-x+1)-1<=n && pos2+pos1-(n-x+1)-1>=pos2) change(1,1,n,pos2,pos2+pos1-(n-x+1)-1);
        }else change(1,1,n,n-x+1,n);
    }
    printf("%d",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值