[BJOI2019] 删数

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

题解

首先我们需要弄清这个答案是什么。

对于一个长度为n的序列,那么它先删的肯定是\(n\),删完之后它就会跳到\(n-cnt[n]\)位置,然后变成子问题继续做 。

于是我们把每个数看做一条覆盖\(n-cnt[n]+1 \sim n\)的一条线段,那么有解的前提是\(1\sim n\)中的每个数都被覆盖了。

如果没有,需要调整多少次呢?

可以发现,我们可以花费一的代价将一条线段的长度-1,再将另一条线段长度+1,可以发现答案就是所有没有被覆盖的位置的长度和。

然后用线段树完成这个操作,整体加的话就将询问区间平移,注意:不被询问区间完全包含的线段要清掉。

代码

#include<bits/stdc++.h>
#define N 150009
#define P pair<int,int>
#define mm make_pair
using namespace std;
typedef long long ll;
int tr[N*12],la[N*12],num[N*12],nowl,nowr,maxn,n,m,a[N],tag;
map<int,int>tong;
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
void build(int cnt,int l,int r){
    num[cnt]=r-l+1;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(cnt<<1,l,mid);build(cnt<<1|1,mid+1,r);
}
inline void pushdown(int cnt){
    la[cnt<<1]+=la[cnt];
    tr[cnt<<1]+=la[cnt];
    la[cnt<<1|1]+=la[cnt];
    tr[cnt<<1|1]+=la[cnt];
    la[cnt]=0;
}
inline P merge(P x,P y){
    P z=x;
    if(y.first<z.first)z=y;
    else if(y.first==z.first)z.second+=y.second;
    return z;
}
inline void pushup(int cnt){
    tr[cnt]=tr[cnt<<1];num[cnt]=num[cnt<<1];
    if(tr[cnt<<1|1]<tr[cnt])tr[cnt]=tr[cnt<<1|1],num[cnt]=num[cnt<<1|1];
    else if(tr[cnt<<1|1]==tr[cnt])num[cnt]+=num[cnt<<1|1];
}
P query(int cnt,int l,int r,int L,int R){
    if(l>=L&&r<=R)return mm(tr[cnt],num[cnt]);
    int mid=(l+r)>>1;
    if(la[cnt])pushdown(cnt);
    if(mid>=L&&mid<R)return merge(query(cnt<<1,l,mid,L,R),query(cnt<<1|1,mid+1,r,L,R));
    if(mid>=L)return query(cnt<<1,l,mid,L,R);
    if(mid<R)return query(cnt<<1|1,mid+1,r,L,R);
}
void upd(int cnt,int l,int r,int L,int R,int tag){
    if(l>=L&&r<=R){
        tr[cnt]+=tag;
        la[cnt]+=tag;
        return;
    }
    int mid=(l+r)>>1;
    if(la[cnt])pushdown(cnt);
    if(mid>=L)upd(cnt<<1,l,mid,L,R,tag);
    if(mid<R)upd(cnt<<1|1,mid+1,r,L,R,tag);
    pushup(cnt);
}
inline void work(int l,int r,int tag){
    l=max(l,nowl);r=min(r,nowr);
    if(l>r)return;
    upd(1,1,maxn,l-nowl,r-nowl,tag);
}
int main(){
    n=rd();m=rd();
    nowl=1-m-1;nowr=n+m+1;
    maxn=nowr-nowl+1;
    build(1,1,maxn);
    int ls=1,rs=n;
    for(int i=1;i<=n;++i)a[i]=rd(),tong[a[i]]++;
    for(int i=1;i<=n;++i)work(i-tong[i]+1,i,1);
    int p,x;
    while(m--){
        p=rd();x=rd();
        if(!p){
            ls-=x;rs-=x;tag-=x;
            if(x<0){
                int xx=rs,yy=ls-1;
                if(tong.find(xx)!=tong.end())work(xx-tong[xx]+1,xx,1);
                if(tong.find(yy)!=tong.end())work(yy-tong[yy]+1,yy,-1);
            }
            else{
                int xx=ls,yy=rs+1;
                if(tong.find(xx)!=tong.end())work(xx-tong[xx]+1,xx,1);
                if(tong.find(yy)!=tong.end())work(yy-tong[yy]+1,yy,-1);
            }
        }
        else{
            x+=tag;
            if(a[p]>=ls&&a[p]<=rs)work(a[p]-tong[a[p]]+1,a[p]-tong[a[p]]+1,-1);
            tong[a[p]]--;
            a[p]=x;
            tong[a[p]]++;
            if(a[p]>=ls&&a[p]<=rs)work(a[p]-tong[a[p]]+1,a[p]-tong[a[p]]+1,1);
        }
        P xx=query(1,1,maxn,ls-nowl,rs-nowl);
        if(xx.first==0)printf("%d\n",xx.second);
        else puts("0");
    }
    return 0;
}

原文地址:https://www.cnblogs.com/ZH-comld/p/10783134.html

时间: 2024-10-11 00:15:16

[BJOI2019] 删数的相关文章

Luogu5324 BJOI2019删数(线段树)

考虑无修改怎么做.对于1~n的每个数,若其存在,将最后一个放在其值的位置,剩余在其前面依次排列,答案即为值域1~n上没有数的位置个数.带修改显然记一下偏移量线段树改一改就好了. #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #

dtoi4375「BJOI2019」删数

题意: 对于任意一个数列,如果能在有限次进行下列删数操作后将其删为空数列,则称这个数列可以删空.一次删数操作定义如下: 记当前数列长度为 k,则删掉数列中所有等于 k 的数. 现有一个长度为 n 的数列 a,有 m 次修改操作,第 i 次修改后你要回答:经过 i 次修改后的数列 a,至少还需要修改几个数才可删空? 每次修改操作为单点修改或数列整体加一或数列整体减一. 题解:      如果一个我要删去大小为a的数,那么序列长度会变成a-h[a](h[a]为数值a出线的次数).那么我们意会一下这个

codevs4096 删数问题

题目描述 Description 键盘输入一个高精度的正整数N,去掉其中任意S个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的N 和S,寻找一种方案使得剩下的数字组成的新数最小. 输入数据均不需要判错. 输出组成的新的正整数.(N不超过240位) 输入描述 Input Description 第一行,输入一正整数N(N<=10240),表示要删的数: 第二行,输入一正整数S,表示删去的个数,当然S小于N的位数. 输出描述 Output Description 仅一行,输出删数后形

洛谷P1106 删数问题

题目描述 键盘输入一个高精度的正整数N,去掉其中任意k个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的N和k,寻找一种方案使得剩下的数字组成的新数最小. 输出应包括所去掉的数字的位置和组成的新的整数.(N不超过250位) 输入数据均不需判错. 输入输出格式 输入格式: n (高精度的正整数) k (需要删除的数字个数) 输出格式: 最后剩下的最小数. 输入输出样例 输入样例#1: 175438 4 输出样例#1: 13分析:贪心思想,我们每次删数肯定是要找前面的大数给删掉,并且这

4906 删数问题

4906 删数问题 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 查看运行结果 题目描述 Description 键盘输入一个高精度的正整数N,去掉其中任意S个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的N 和S,寻找一种方案使得剩下的数字组成的新数最小. 输入数据均不需要判错. 输出组成的新的正整数.(N不超过240位) 输入描述 Input Description 第一行,输入一正整数N(N<=10240),表示要删的数: 第二行,

1106 删数问题

1106 删数问题 难度:普及/提高- 题目类型:贪心 提交次数:3 涉及知识:字符串.贪心 题目描述 键盘输入一个高精度的正整数N,去掉其中任意k个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的N和k,寻找一种方案使得剩下的数字组成的新数最小. 输出应包括所去掉的数字的位置和组成的新的正整数.(N不超过250位) 输入数据均不需判错. 代码: #include<iostream> #include<cstring> using namespace std; int

删数问题--贪心

删数问题 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描述 给定n 位(n≤100)正整数a,去掉其中任意k≤n 个数字后,剩下的数字按原次序排列组成一个新的正整数.对于给定的n 位正整数a和正整数k,设计一个算法找出剩下数字组成的新数最小的删数方案. 对于给定的正整数a,计算删去k 个数字后得到的最小数. 输入 输入数据的第1 行是1 个正整数a.第2 行是正整数k. 输出 将计算出的最小数输出. 示例输入 178543 4

算法课作业之删数问题

问题描述: 通过键盘输入一个高精度的正整数n(n的有效位数≤240),去掉其中任意s个数字后,剩下的数字按原左右次序将组成一个新的正整数.编程对给定的n和s,寻找一种方案,使得剩下的数字组成的新数最小. 问题分析: 这个问题是最优子结构问题,即局部最优能决定全局最优解,可以使用贪心算法进行解决.n个正整数去掉s个数字,求使得到的新的正整数最大的删除方案可以等价为:对于n个正整数组成的数字,一个一个地依次去掉s个数字,要求每删除一个数时,都使删除后的新的正整数最小.因此问题转化为求解删除一个数字时

【贪心】删数问题

[贪心]删数问题 题目描述 输入一个高精度的正整数n(≤240位),去掉其中任意s个数字后,剩下的数字按原左右次序组成一个新的正整数.编程对给定的n和s,寻找一种方案,使得剩下的数字组成的新数最小. 输入 第1行:一个正整数n: 第2行:s(s<n的位数). 输出 最后剩下的最小数. 样例输入 175438 4 样例输出 13分析:结论是删去第一个递减的数,如果没有则删去最后一个数:代码: #include <iostream> #include <cstdio> #incl