P5324 [BJOI2019]删数 (线段树 求区间 0 的个数)

题目描述

对于任意一个数列,如果能在有限次进行下列删数操作后将其删为空数列,则称这个数列可以删空。一次删数操作定义如下:

记当前数列长度为kk,则删掉数列中所有等于kk的数。

现有一个长度为nn的数列aa,有mm次修改操作,第ii次修改后你要回答:
经过ii次修改后的数列aa,至少还需要修改几个数才可删空?

每次修改操作为单点修改或数列整体加一或数列整体减一。

输入输出格式

输入格式:

 

第一行两个正整数n,mn,m,分别表示数列长度、修改次数。
第二行有nn个正整数,表示数列aa,即输入的第ii个数表示数列aa的第ii个数a_iai​。

接下来mm行,每行两个整数p,xp,x,表示一次修改操作。
当1\le p \le n1≤p≤n时,该操作为单点修改,将数列中第pp个数a_pap​修改为xx
当p=0p=0时,该操作为数列整体加xx。

 

输出格式:

 

输出mm行,每行一个整数,第ii行表示前ii次修改后的答案。

思路:

简单想一想:

每个数出现的次数用一个桶表示出来。 形成一个个的柱子,然后向左推倒, 答案就是空位的个数。

注意:只有在区间内的点才可以向左推倒。

单点修改 和 区间修改。

 

具体看洛谷的题解吧。挺详细的。

https://www.luogu.org/problemnew/solution/P5324

#include
#define ls (now << 1)
#define rs (now << 1 | 1)
using namespace std;
const int N = 1e6;
int n,m,op,z,pos,tot;
int cnt[N],a[N],c[N];
int mn[N*4],val[N*4],tag[N*4],sum[N*4];
// mn val tag sum
//区间最小值, 0 的个数, 标记, 最小值的个数,,
// 0 的个数可以由 最小值的个数转移过来。
void pushup(int now){
    mn[now] = min(mn[ls],mn[rs]);
    sum[now] = (mn[now] == mn[ls]?sum[ls]:0) + (mn[now] == mn[rs]?sum[rs]:0);
    val[now] = val[ls] + val[rs];
}
void pushdown(int now){
    if (tag[now] == 0) return;
    mn[ls] += tag[now];
    val[ls] = (mn[ls] == 0 ? sum[ls]: 0); 
    tag[ls] += tag[now];

    mn[rs] += tag[now];
    val[rs] = (mn[rs] == 0 ? sum[rs]:0);
    tag[rs] += tag[now]; 
    tag[now]  = 0;
}
void build(int now, int l, int r){
    if (l + 1 == r){
        val[now] = sum[now] = 1;
        return;
    }
    int mid = (l + r) / 2;
    build(ls,l,mid);
    build(rs,mid,r);
    pushup(now);
}
void Insert(int now, int l, int r, int a,int b, int k){
    if (a <= l && b >= r-1){
        mn[now] += k;
        val[now] = (mn[now] == 0?sum[now]:0);
        tag[now] += k;
        return;
    }
    int mid = (l + r) >> 1;
    pushdown(now);
    if (a < mid) Insert(ls,l,mid,a,b,k);
    if (b >= mid) Insert(rs,mid,r,a,b,k);
    pushup(now);
}
int find(int now, int l, int r, int a, int b){
    int ans = 0;
    if (a <= l && b >= r-1){
        return val[now];
    }
    pushdown(now);
    int mid = (l + r) >> 1;
    if (a < mid) ans += find(ls,l,mid,a,b);
    if (b >= mid) ans += find(rs,mid,r,a,b);
    return ans;
}

int main(){
    scanf("%d%d",&n,&m);
    pos = 2e5+10;
    tot = 6e5+10;
    for (int i = 1; i <= n; ++i){
        scanf("%d",&z);
        a[i] = pos + z;
        cnt[a[i]]++;
    } 
    build(1,1,tot);
    for (int i = pos + 1; i <= pos + n; ++i)
        if (cnt[i]) Insert(1,1,tot,i-cnt[i]+1,i,1);
    for (int i = 0; i < m; ++i){
        scanf("%d%d",&op,&z);
        if (op){
            int tmp = a[op] - cnt[a[op]] + 1;
            cnt[a[op]]--;
            if (a[op] <= pos + n)
            Insert(1,1,tot,tmp,tmp,-1);
            a[op] = pos + z;
            cnt[a[op]]++;
            tmp = a[op] - cnt[a[op]] + 1;
            if (a[op] <= pos + n)
            Insert(1,1,tot,tmp,tmp,1);
            printf("%d\n",find(1,1,tot,pos+1,pos+n));
        } else{ 
            if (z == 1){
                Insert(1,1,tot,pos+n-cnt[pos+n]+1,pos + n,-1);
                pos--;
            } else {
                pos++;
                Insert(1,1,tot,pos+n-cnt[pos+n]+1,pos+n,1);
            }
            printf("%d\n",find(1,1,tot,pos+1,pos+n));
        }
    }

    return 0;
}

 

你可能感兴趣的:(#,线段树,树状数组,线段树)