题面

传送门

思路

dp部分

以下称合法序列为原题面中可以删空的序列

这个是我在模拟考场上的思路

一开始我是觉得,这个首先可以写成一个dp的形式:$dp[i][j]$表示用$j$个数字填满了目标序列的前$i$需要的步数

然后,发现只有$dp[i][i]$有意义,所以优化为$dp[i]$表示达成了构成长度为$i$的序列需要的最小步数

猜一个转移方程:$dp[i]=min_{j\in[1,i-1]}(dp[j]+max(0,(i-j)-num[i])$

这里$num[i]$表示当前询问的序列中数字$i$的出现次数,就是一个桶

转移方程的意义就是在一个长度为$j$的合法序列(下称$j$序列)后面接上$i-j$个$i$正确性证明如下:

首先,我们可以把原序列中所有等于$i$的数字直接用上,够了就不用变新的,不够了就从别的数字那里拿一些变过来补上

问题:如何确定数字等于$i$的没有在前面的$j$序列中被转移过去?

可以发现这里不需要考虑这种情况:转移过去的只需要是和在前面需要的数大小不一样的

$i$显然不和$j$序列中任何一个数相等,所以即使用过了$i$,也可以随便挑一个$i$以外的代替$i$进去前面

然后因为最终一共$n$个数填长度为$n$的序列,数总是够用的,所以这就完成了$dp$转移正确性的证明

这样可以获得47分

贪心部分

上面那个结论其实还有用处:它是贪心做法的基础

可以发现只有在$[1,n]$区间内的数是“有用”,即有可能不需要改变就可以放进合法序列里的

我们考虑这些数的“有用性”,发现:对于$j$个$i$,里面最多有$i$个是有用的

同时,如果有两种数$i<j$,令$k=j-i$,若值为$j$的数的数量大于$k$,那么这里$j$和$i$就会“冲突”,也就是$i$和以下的一小段位置只有一种数可以不修改放进去

形式化地来说,设值为$i$的数字有$cnt[i]$个,那么它们可以覆盖区间$[i-cnt[i]+1,i]$

显然,区间$[1,n]$内没有被覆盖的位置总数,就是这个询问的答案

数据结构优化

显然我们可以通过线段树维护最小值和最小值个数来解决这个问题

考虑两种修改

第一种修改是修改两个区间的长度,就是两次单点修改而已

第二种修改可以相当于平移询问区间

这里要注意:只有值在询问区间里的位置才能往前覆盖

也就是说,假设我询问的是$[2,5]$,原序列里面有$3$个$6$,那这些6都没有用

所以需要在第二种修改的时候看看会不会加入or删除整个区间

实现上因为可能会减到负数,负数还能往下覆盖,所以一共开长度为$2n+2m$的线段树,初始0位置$n+m$

Code

这代码写的我好气啊

终端里面写完代码,:wq退出vim,然后习惯性按两下上箭头用gedit t1.cpp打开代码往虚拟机外面复制

结果按两下向上却执行了从外部copy模板到t1.cpp,然后写好的代码就被覆盖了......

然后我手忙脚乱,想重新打开编译好的执行文件,结果又不小心按上执行了编译命令,编译一个没有main()的模板

随着编译器提示编译失败,我原来的可执行程序也消失了......decompile都莫得机会

只好重新写一遍......

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstring>
  4. #include<algorithm>
  5. #include<cassert>
  6. #define ll long long
  7. using namespace std;
  8. inline int read(){
  9. int re=0,flag=1;char ch=getchar();
  10. while(!isdigit(ch)){
  11. if(ch=='-') flag=-1;
  12. ch=getchar();
  13. }
  14. while(isdigit(ch)) re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
  15. return re*flag;
  16. }
  17. int n,m,a[1000010],cnt[1000010],ori[1000010],pos;
  18. struct ele{//合并用的线段树元素,query的时候很方便,merge就好了
  19. int minn,cnt;
  20. ele(int mm=0,int cc=0){minn=mm;cnt=cc;}
  21. friend inline ele merge(const ele &a,const ele &b){
  22. ele re;re.minn=min(a.minn,b.minn);
  23. if(re.minn==a.minn) re.cnt+=a.cnt;
  24. if(re.minn==b.minn) re.cnt+=b.cnt;
  25. assert(re.minn>=0);
  26. return re;
  27. }
  28. }seg[2000010];int tag[2000010];//有区间修改,加lazytag
  29. inline void pushtag(int num,int w){seg[num].minn+=w;tag[num]+=w;assert(seg[num].minn>=0);}
  30. inline void push(int l,int r,int num){
  31. if(l==r||!tag[num]) return;
  32. pushtag(num<<1,tag[num]);
  33. pushtag(num<<1|1,tag[num]);
  34. tag[num]=0;
  35. }
  36. inline void build(int l,int r,int num){
  37. if(l==r){seg[num]=ele(ori[l],1);return;}
  38. int mid=(l+r)>>1;
  39. build(l,mid,num<<1);build(mid+1,r,num<<1|1);
  40. seg[num]=merge(seg[num<<1],seg[num<<1|1]);
  41. }
  42. inline void change(int l,int r,int num,int pos,int w){
  43. if(l==r){seg[num].minn+=w;return;}
  44. int mid=(l+r)>>1;push(l,r,num);
  45. if(mid>=pos) change(l,mid,num<<1,pos,w);
  46. else change(mid+1,r,num<<1|1,pos,w);
  47. seg[num]=merge(seg[num<<1],seg[num<<1|1]);
  48. }
  49. inline void add(int l,int r,int ql,int qr,int num,int w){
  50. if(l>=ql&&r<=qr){pushtag(num,w);return;}
  51. int mid=(l+r)>>1;push(l,r,num);
  52. if(mid>=ql) add(l,mid,ql,qr,num<<1,w);
  53. if(mid<qr) add(mid+1,r,ql,qr,num<<1|1,w);
  54. seg[num]=merge(seg[num<<1],seg[num<<1|1]);
  55. }
  56. inline ele query(int l,int r,int ql,int qr,int num){
  57. if(l>=ql&&r<=qr) return seg[num];
  58. int mid=(l+r)>>1;ele re(2e9,0);push(l,r,num);
  59. if(mid>=ql) re=merge(re,query(l,mid,ql,qr,num<<1));
  60. if(mid<qr) re=merge(re,query(mid+1,r,ql,qr,num<<1|1));
  61. return re;
  62. }
  63. int main(){
  64. n=read();m=read();int i,t1,t2,j,tot=n+n+m+m;ele ans;
  65. pos=n+m;
  66. for(i=1;i<=n;i++) cnt[(a[i]=read()+pos)]++;
  67. for(i=1;i<=tot;i++) if(cnt[i])
  68. for(j=i;j>i-cnt[i];j--) ori[j]++;
  69. build(1,tot,1);
  70. while(m--){
  71. t1=read();t2=read();
  72. if(t1){
  73. //直接维护,注意不在目前范围内的位置不要change!!!
  74. t2+=pos;
  75. cnt[a[t1]]--;ori[a[t1]-cnt[a[t1]]]--;
  76. if(a[t1]<=pos+n&&a[t1]>pos) change(1,tot,1,a[t1]-cnt[a[t1]],-1);
  77. a[t1]=t2;
  78. if(a[t1]<=pos+n&&a[t1]>pos) change(1,tot,1,a[t1]-cnt[a[t1]],1);
  79. ori[a[t1]-cnt[a[t1]]]++;cnt[a[t1]]++;
  80. }
  81. else{
  82. //查看是否有区间需要加入or删除!!!
  83. if(~t2){
  84. pos--;
  85. if(cnt[pos+n+1]) add(1,tot,pos+n+1-cnt[pos+n+1]+1,pos+n+1,1,-1);
  86. if(cnt[pos+1]) add(1,tot,pos+1-cnt[pos+1]+1,pos+1,1,1);
  87. }
  88. else{
  89. pos++;
  90. if(cnt[pos+n]) add(1,tot,pos+n-cnt[pos+n]+1,pos+n,1,1);
  91. if(cnt[pos]) add(1,tot,pos-cnt[pos]+1,pos,1,-1);
  92. }
  93. }
  94. ans=query(1,tot,pos+1,pos+n,1);
  95. if(ans.minn) puts("0");
  96. else printf("%d\n",ans.cnt);
  97. }
  98. }

[BJOI2019] 删数 [dp转贪心结论+线段树]的更多相关文章

  1. LOJ 3094 「BJOI2019」删数——角标偏移的线段树

    题目:https://loj.ac/problem/3094 弱化版是 AGC017C . 用线段树维护那个题里的序列即可. 对应关系大概是: 真实值的范围是 [ 1-m , n+m ] :考虑设偏移 ...

  2. [BJOI2019]删数(线段树)

    [BJOI2019]删数(线段树) 题面 洛谷 题解 按照值域我们把每个数的出现次数画成一根根的柱子,然后把柱子向左推导,\([1,n]\)中未被覆盖的区间长度就是答案. 于是问题变成了单点修改值,即 ...

  3. [Luogu5324][BJOI2019]删数(线段树)

    CF风格题,先猜结论,记数列中i这个数共出现了cnt[i]次,那么所有区间[i-cnt[i]+1,i]的并集的补集大小就是答案. 于是我们只需要线段树维护每个位置是否被某个区间覆盖到即可,对于整体加减 ...

  4. Luogu5324 BJOI2019删数(线段树)

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

  5. luogu P5324 [BJOI2019]删数

    传送门 不如先考虑暴力,能删的序列首先有\(1,2,3...n\),还有就是升序排序后从后往前放数,第\(i\)位要么放\(i\),要么放\(i+1\)位置的数,例如\(1,2,4,4,5,6,9,9 ...

  6. 题解 洛谷 P5324 【[BJOI2019]删数】

    先考虑对于一个序列,能使其可以删空的的修改次数. 首先可以发现,序列的排列顺序是没有影响的,所以可以将所有数放到桶里来处理. 尝试对一个没有经过修改的可以删空的序列来进行删数,一开始删去所有的\(n\ ...

  7. [BJOI2019] 删数

    https://www.luogu.org/problemnew/show/P5324 题解 首先我们需要弄清这个答案是什么. 对于一个长度为n的序列,那么它先删的肯定是\(n\),删完之后它就会跳到 ...

  8. 【题解】Luogu P5324 [BJOI2019]删数

    原题传送门 易知这个数列的顺序是不用考虑的 我们看两个数列 \(1,2,3\)和\(3,3,3\)都能删完,再看两个数列\(1,2,3,4\)和\(2,2,4,4\),也都能删完 不难发现,我们珂以把 ...

  9. [DP][SA][可持久化线段树]黑红兔

    源自 xyz32768 菜鸡的 FJ 省冬令营模拟赛题 原题 CF1063F Statement 给定一个长度为 \(n\) 的字符串 \(s\),仅包含小写英文字母 要从中从左往右选出若干段不相交的 ...

随机推荐

  1. 洛谷P1019 单词接龙题解(超详细注释)

    https://www.luogu.org/problem/P1019 #include<cstdio> #include<cstring> #include<iostr ...

  2. 洛谷P3124被困在haybales

    题目 按理来说是可以二分的,但是发现其实直接暴力然后注意细节就可以了. 先找到牛所在的起点,然后分别向右找和向左找. 第一次查找从\(r\)点冲到\(l\)点时,突破不了\(l\),从\(l\)点冲到 ...

  3. 模板 - 数学 - 数论 - Min_25筛

    终于知道发明者的正确的名字了,是Min_25,这个筛法速度为亚线性的\(O(\frac{n^{\frac{3}{4}}}{\log x})\),用于求解具有下面性质的积性函数的前缀和: 在 \(p\) ...

  4. 用avalon框架怎么做轮播图?

    avalon这个框架其实特别的小众,做个轮播图呢?在github上的例子只有一个,而且功能特别的少,有的引入的插件与avalon里面的指令又不兼容,所以找了一个owl-carousel,目前实现了移动 ...

  5. 常用spaceclaim脚本(二)

    #创建一个草图 #第一个参数传入一个Frame对象 #通过一个点和两个向量创建Frame #Frame的类成员函数Create被重载 #重载函数1:Frame.Create(Point, Direct ...

  6. JavaScript初探系列目录

    一  系列导航 结合各方面的参考资料,整理出来以下主要目录,供方便浏览查看 (一)初探系列           JavaScript初探系列(1)——基本概念 JavaScript初探系列(2)——数 ...

  7. 【软工实践】Alpha冲刺(2/6)

    链接部分 队名:女生都队 组长博客: 博客链接 作业博客:博客链接 小组内容 恩泽(组长) 过去两天完成了哪些任务 描述 了解了如何根据系统获取的实际情况进行后端任务的调整 网易云音乐推荐算法的分析 ...

  8. 常用的maven仓库地址

    maven 仓库地址: 共有的仓库http://repo1.maven.org/maven2/http://repository.jboss.com/maven2/http://repository. ...

  9. windows 共享文件夹,和共享打印机

    达成的情形,目标主机上登陆用户设置密码,其他pc上需要目标主机的用户和密码才能访问其共享文件夹 步骤:1.目标主机,设置文件夹共享    在文件夹上右键-属性,点击共享选项卡,然后点击共享按钮,继续点 ...

  10. MITMProxy如何配置二次代理

    MITMProxy如何配置二次代理 0.2172018.09.05 11:13:15字数 232阅读 2609 前序: mitmproxy真的很强大,或许是大家都各自使用,或者没有相关的需求,导致我废 ...