CodeForce 1354 D. Multiset【二分】

题目链接
  想到的第一种做法是用树状数组维护每个数出现次数的前缀和。插入某个数就比较简单,树状数组单点修改即可。删除第 k k k个数的话就要先二分 p r e f i > = k pref_i >= k prefi>=k的位置,换句话说:找到一个最小的数 v a l val val使得 ∑ i = 1 v a l c n t i    ≥    k \sum_{i=1}^{val}cnt_i \; ≥ \; k i=1valcntik,然后val的出现次数 − 1 -1 1,这个复杂度带两个 l o g log log,904ms.

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const long long mode = 1e9+7;

const int N = 1000010;
struct FenwickTree{
    int sum[N];
    void init(int n){
        for(int i=0;i<=n;i++)sum[i] = 0;
    }
    void Add(int x,int n,int d){
        while( x<=n ){
            sum[x] += d;
            x += (x&(-x));
        }
    }
    int getSum(int x){
        int res = 0;
        while( x> 0 ){
            res += sum[x];
            x -= (x&(-x));
        }
        return res;
    }
};

FenwickTree cnt;
int a[N],q[N];

int bs(int left,int right,int num){
    while( left <= right ){
        int mid = (left+right) / 2;
        if( cnt.getSum( mid ) < num )left = mid+1;
        else right = mid-1;
    }
    return left;
}

int main()
{
    int T = 1;
    // scanf("%d",&T);
    while(T--){
        int n,Q;
        scanf("%d %d",&n,&Q);

        for(int i=1;i<=n;i++)scanf("%d",a+i);
        for(int i=1;i<=Q;i++)scanf("%d",q+i);

        for(int i=1;i<=n;i++)cnt.Add(a[i],n,1);

        for(int i=1;i<=Q;i++){
            if( q[i] > 0 )cnt.Add( q[i],n,1 );
            else{
                int index = bs( 1,n,-q[i] );
                cnt.Add( index,n,-1 );
            }
        }

        int index = bs(1,n,1);
        printf("%d\n", ( index>n ? 0 : index));
    }
    return 0;
}

  看了题解的最优解,二分最后结果序列中最小的数 m i d mid mid c h e c k check check:维护两个计数器 l e s s O r E q u a l lessOrEqual lessOrEqual g r e a t e r greater greater,分别记录 < = m i d <=mid <=mid 的数的个数和 > m i d >mid >mid 的数的个数。对于修改:插入一个 q [ i ] q[i] q[i]直接判断是否大于 m i d mid mid,删除第 k k k个就是和 l e s s O r E q u a l lessOrEqual lessOrEqual比较,比它大 g r e a t e r − = 1 greater -= 1 greater=1,否则 l e s s O r E q u a l − = 1 lessOrEqual-=1 lessOrEqual=1。最后返回 l e s s O r E q u a l > 0 lessOrEqual > 0 lessOrEqual>0即可。561ms

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const long long mode = 1e9+7;

const int N = 1000010;

int a[N],q[N];

bool check(int mid,int n,int Q){
    int le = 0, great = 0;
    for(int i=1;i<=n;i++){
        if( a[i] <= mid )le++;
        else great++;
    }

    for(int i=1;i<=Q;i++){
        if( q[i] > 0 ){
            if( q[i] > mid )great++;
            else le++;
        }
        else{
            if( -q[i] > le )great--;
            else le--;
        }
    }
    // printf("check %d : %d %d\n",mid,le,great);
    return le > 0;
}


int main()
{
    int T = 1;
    // scanf("%d",&T);
    while(T--){
        int n,Q;
        scanf("%d %d",&n,&Q);

        for(int i=1;i<=n;i++)scanf("%d",a+i);
        for(int i=1;i<=Q;i++)scanf("%d",q+i);

        int bottom = 1,top = n;
        while( bottom <= top ){
            int mid = (bottom + top) / 2;
            if( check( mid,n,Q ) )top = mid - 1;
            else bottom = mid + 1;
        }

        printf("%d\n",( top+1 > n ? 0 : top+1 ));
        
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值