题目

https://www.luogu.com.cn/problem/P5356

思路

由乃题,那么考虑分块(大雾,但确实分块是正解)。

题面很清晰,就是求动态的区间第k小,支持区间加法操作。

根据套路,需要维护一个原数组a,每个块的加法标记add,还有对原数组进行块内排序的结果c。

考虑到第k小这个东西不好维护,对于查询操作我们在外面套一层二分,来二分答案x,统计区间内小于x的值的个数。

那这个东西就好算了,每个块内部从小到大排序,对于整块的调用一下\(lower\)_\(bound\),两边的零头暴力统计就好(注意暴力时访问的是a数组)。

对于区间加法操作,整块的显然维护一下块的加法标记就好,因为每个数加的一样不改变次序。零头比较麻烦,因为可能会破坏所在块的有序性。

那我们直接暴力重构这些块,考虑到一次区间加法操作最多有两个块需要重构,代价可以接受(事实上更好的做法是记录该块需要重构,查询操作遇到该块时才真正重构它)。

一些细节就直接看代码吧,语言能力不好表述不清楚。。。

代码(version1)

点击查看代码
  1. #include<cstdio>
  2. #include<cstdlib>
  3. #include<algorithm>
  4. #include<cmath>
  5. #define inf 0x3f3f3f3f
  6. #define maxn 100010
  7. using namespace std;
  8. int a[maxn],block,add[maxn],b[maxn],cnt,unset[maxn];
  9. int L[maxn],R[maxn];
  10. int c[maxn],MAX=-inf,MIN=inf;
  11. void reset(int x){
  12. int i;
  13. for(i=L[x];i<=R[x];++i) a[i]+=add[x];
  14. for(i=L[x];i<=R[x];++i) c[i]=a[i];
  15. sort(c+L[x],c+R[x]+1);
  16. add[x]=0;
  17. unset[x]=0;
  18. return;
  19. }
  20. int Count(int l,int r,int k){
  21. int i,ans=0;
  22. if(b[l]==b[r]){
  23. for(i=l;i<=r;++i) if(a[i]+add[b[i]]<k) ans++;
  24. return ans;
  25. }
  26. for(i=b[l]+1;i<b[r];++i){
  27. if(unset[i]) reset(i);
  28. ans+=lower_bound(c+L[i],c+R[i]+1,k-add[i])-(c+L[i]);
  29. }
  30. for(i=l;i<=R[b[l]];++i) ans+=(a[i]+add[b[i]]<k);
  31. for(i=L[b[r]];i<=r;++i) ans+=(a[i]+add[b[i]]<k);
  32. return ans;
  33. }
  34. int query(int l,int r,int k){
  35. int x=MIN,y=MAX;
  36. if(k>r-l+1) return -1;
  37. while(x<y){
  38. int mid=(x+y+1)>>1;
  39. int t=Count(l,r,mid);
  40. if(t>k-1) y=mid-1;
  41. else x=mid;
  42. }
  43. return x;
  44. }
  45. void modify(int l,int r,int k){
  46. int i,j;
  47. if(k>0) MAX+=k;
  48. if(k<0) MIN+=k;
  49. if(b[l]==b[r]){
  50. for(i=l;i<=r;++i) a[i]+=k;
  51. unset[b[l]]=1;
  52. return;
  53. }
  54. for(i=b[l]+1;i<b[r];++i) add[i]+=k;
  55. for(i=l;i<=R[b[l]];++i) a[i]+=k;
  56. for(i=L[b[r]];i<=r;++i) a[i]+=k;
  57. unset[b[l]]=unset[b[r]]=1;
  58. }
  59. int main(){
  60. int n,m,i,j,opt,x,l,r;
  61. // freopen("test.in","r",stdin);
  62. // freopen("test.out","w",stdout);
  63. scanf("%d%d",&n,&m);
  64. for(i=1;i<=n;++i){
  65. scanf("%d",&a[i]);
  66. MAX=max(MAX,a[i]);
  67. MIN=min(MIN,a[i]);
  68. }
  69. block=max(2,(int)sqrt(0.6*n));
  70. for(i=1;i<=n;++i) b[i]=(i-1)/block+1;
  71. cnt=b[n];
  72. for(i=1;i<=n;++i) c[i]=a[i];
  73. for(i=1;i<=cnt;++i) L[i]=(i-1)*block+1,R[i]=min(i*block,n);
  74. for(i=1;i<=cnt;++i) sort(c+L[i],c+R[i]+1);
  75. for(i=1;i<=m;++i){
  76. scanf("%d%d%d%d",&opt,&l,&r,&x);
  77. if(opt==1) printf("%d\n",query(l,r,x));
  78. else modify(l,r,x);
  79. }
  80. return 0;
  81. }

优化

然后我们发现这个代码T飞了,粗略估计一下,这个东西复杂度是鬼畜的\(m\sqrt nlognlog(二分范围)\),再考虑人傻常数大的因素明显会寄。

当时我自己调的时候想法就是减少二分范围,维护一个不紧的上下界,然后再调整一下块长,可是死活过不去。

然后贺一波题解,发现了一个很玄学的优化:

考虑到数的值域很小,那么很有可能块内每个数的大小都差不多。这意味着什么呢?这就是说,二分的时候,很可能一整个块的数都整体大于或整体小于二分的值\(x\)。

于是我们判一下块头元素和块尾元素,如果头比\(x\)大或尾比\(x\)小就可以直接跳过二分的过程。

加上玄学优化就跑得很快。

代码(version2)

点击查看代码
  1. #include<cstdio>
  2. #include<cstdlib>
  3. #include<algorithm>
  4. #include<cmath>
  5. #define inf 0x3f3f3f3f
  6. #define maxn 100010
  7. using namespace std;
  8. int a[maxn],block,add[maxn],b[maxn],cnt,unset[maxn];
  9. int L[maxn],R[maxn];
  10. int c[maxn];
  11. void reset(int x){
  12. int i;
  13. for(i=L[x];i<=R[x];++i) a[i]+=add[x];
  14. for(i=L[x];i<=R[x];++i) c[i]=a[i];
  15. sort(c+L[x],c+R[x]+1);
  16. add[x]=0;
  17. unset[x]=0;
  18. return;
  19. }
  20. int Count(int l,int r,int k){
  21. int i,ans=0;
  22. if(b[l]==b[r]){
  23. for(i=l;i<=r;++i) if(a[i]+add[b[i]]<k) ans++;
  24. return ans;
  25. }
  26. for(i=b[l]+1;i<b[r];++i){
  27. if(c[L[i]]>=k-add[i]) continue;
  28. if(c[R[i]]<k-add[i]){
  29. ans+=R[i]-L[i]+1;
  30. continue;
  31. }
  32. ans+=lower_bound(c+L[i],c+R[i]+1,k-add[i])-(c+L[i]);
  33. }
  34. for(i=l;i<=R[b[l]];++i) ans+=(a[i]+add[b[i]]<k);
  35. for(i=L[b[r]];i<=r;++i) ans+=(a[i]+add[b[i]]<k);
  36. return ans;
  37. }
  38. int query(int l,int r,int k){
  39. int x=inf,y=-inf;
  40. for(int i=b[l];i<=b[r];++i){
  41. if(unset[i]) reset(i);
  42. x=min(x,c[L[i]]+add[i]);
  43. y=max(y,c[R[i]]+add[i]);
  44. }
  45. if(k>r-l+1) return -1;
  46. while(x<y){
  47. int mid=(x+y+1)>>1;
  48. int t=Count(l,r,mid);
  49. if(t>k-1) y=mid-1;
  50. else x=mid;
  51. }
  52. return x;
  53. }
  54. void modify(int l,int r,int k){
  55. int i,j;
  56. if(b[l]==b[r]){
  57. for(i=l;i<=r;++i) a[i]+=k;
  58. unset[b[l]]=1;
  59. return;
  60. }
  61. for(i=b[l]+1;i<b[r];++i) add[i]+=k;
  62. for(i=l;i<=R[b[l]];++i) a[i]+=k;
  63. for(i=L[b[r]];i<=r;++i) a[i]+=k;
  64. unset[b[l]]=unset[b[r]]=1;
  65. }
  66. int main(){
  67. int n,m,i,j,opt,x,l,r;
  68. // freopen("test.in","r",stdin);
  69. // freopen("test.out","w",stdout);
  70. scanf("%d%d",&n,&m);
  71. for(i=1;i<=n;++i) scanf("%d",&a[i]);
  72. block=max(2,(int)(sqrt(n*0.7)));
  73. for(i=1;i<=n;++i) b[i]=(i-1)/block+1;
  74. cnt=b[n];
  75. for(i=1;i<=n;++i) c[i]=a[i];
  76. for(i=1;i<=cnt;++i) L[i]=(i-1)*block+1,R[i]=min(i*block,n);
  77. for(i=1;i<=cnt;++i) sort(c+L[i],c+R[i]+1);
  78. for(i=1;i<=m;++i){
  79. scanf("%d%d%d%d",&opt,&l,&r,&x);
  80. if(opt==1) printf("%d\n",query(l,r,x));
  81. else modify(l,r,x);
  82. }
  83. return 0;
  84. }

洛谷P5356 [Ynoi2017] 由乃打扑克的更多相关文章

  1. 洛谷P2668 斗地主==codevs 4610 斗地主[NOIP 2015 day1 T3]

    P2668 斗地主 326通过 2.6K提交 题目提供者洛谷OnlineJudge 标签搜索/枚举NOIp提高组2015 难度提高+/省选- 提交该题 讨论 题解 记录 最新讨论 出现未知错误是说梗啊 ...

  2. 洛谷1640 bzoj1854游戏 匈牙利就是又短又快

    bzoj炸了,靠离线版题目做了两道(过过样例什么的还是轻松的)但是交不了,正巧洛谷有个"大牛分站",就转回洛谷做题了 水题先行,一道傻逼匈牙利 其实本来的思路是搜索然后发现写出来类 ...

  3. 洛谷P1352 codevs1380 没有上司的舞会——S.B.S.

    没有上司的舞会  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 钻石 Diamond       题目描述 Description Ural大学有N个职员,编号为1~N.他们有 ...

  4. 洛谷P1108 低价购买[DP | LIS方案数]

    题目描述 “低价购买”这条建议是在奶牛股票市场取得成功的一半规则.要想被认为是伟大的投资者,你必须遵循以下的问题建议:“低价购买:再低价购买”.每次你购买一支股票,你必须用低于你上次购买它的价格购买它 ...

  5. 洛谷 P2701 [USACO5.3]巨大的牛棚Big Barn Label:二维数组前缀和 你够了 这次我用DP

    题目背景 (USACO 5.3.4) 题目描述 农夫约翰想要在他的正方形农场上建造一座正方形大牛棚.他讨厌在他的农场中砍树,想找一个能够让他在空旷无树的地方修建牛棚的地方.我们假定,他的农场划分成 N ...

  6. 洛谷P1710 地铁涨价

    P1710 地铁涨价 51通过 339提交 题目提供者洛谷OnlineJudge 标签O2优化云端评测2 难度提高+/省选- 提交  讨论  题解 最新讨论 求教:为什么只有40分 数组大小一定要开够 ...

  7. 洛谷P1371 NOI元丹

    P1371 NOI元丹 71通过 394提交 题目提供者洛谷OnlineJudge 标签云端评测 难度普及/提高- 提交  讨论  题解 最新讨论 我觉得不需要讨论O long long 不够 没有取 ...

  8. 洛谷P1538迎春舞会之数字舞蹈

    题目背景 HNSDFZ的同学们为了庆祝春节,准备排练一场舞会. 题目描述 在越来越讲究合作的时代,人们注意的更多的不是个人物的舞姿,而是集体的排列. 为了配合每年的倒计时,同学们决定排出——“数字舞蹈 ...

  9. 洛谷八月月赛Round1凄惨记

    个人背景: 上午9:30放学,然后因为学校举办读书工程跟同学去书城选书,中午回来开始打比赛,下午又回老家,中间抽出一点时间调代码,回家已经8:50了 也许是7月月赛时“连蒙带骗”AK的太幸运然而因同学 ...

  10. 洛谷 P1379 八数码难题 Label:判重&&bfs

    特别声明:紫书上抄来的代码,详见P198 题目描述 在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字.棋盘中留有一个空格,空格用0来表示.空格周围的棋子可以移到空格中.要求解的问题是:给 ...

随机推荐

  1. python GIL解释器

    1.GIL是什么? GIL全称Global Interpreter Lock,即全局解释器锁. 作用就是,限制多线程同时执行,保证同一时间内只有一个线程在执行. GIL并不是Python的特性,它是在 ...

  2. ALBERT论文简读

    问题描述 预训练自然语言表征时,增加模型的参数量通常可以是模型在下有任务中性能提升.但是这种做法对硬件设备的要求较高(当下的各种SOTA模型动辄数亿甚至数十亿个参数,倘若要扩大模型规模,这个内存问题是 ...

  3. VMvare虚拟机的安装及新建虚拟机(一)

    a:hover { color: rgba(255, 102, 0, 1) } 一.VMvare虚拟机的安装 1.首先双击--你下载的安装包,这里我分享百度云盘,供大家下载:http://pan.ba ...

  4. CentOS 7--Nginx安装

    1.安装依赖 yum install -y gcc-c++pcre pcre-develzlib zlib-developenssl openssl-devel 2.下载Nginx wget http ...

  5. T137288 铸星

    有以下三种情况: 只有一个数,直接输出. 有正数也有复数,那就先让负数撞正数得到更小的负数,直到剩下最后一个正数撞所有的负数,答案是所有数的绝对值之和. 仅有正数或者仅有负数,就先找一对不相等的相邻的 ...

  6. redis的windows下安装

    转载https://www.runoob.com/redis/redis-install.html Redis 安装 Windows 下安装 下载地址:https://github.com/tpora ...

  7. selenium------关于switch_to的用法场景

    基于python3的语法,driver.switch_to_alert()的表达会出现中划线,因此需要把后面的下划线改为点.一.目前接触到的switch_to的用法包括以下几种:1. 切换到制定的wi ...

  8. 解决在Android studio的Button控件下background背景设置不起作用的问题

    Button控件默认的背景是深紫色的,有时候会看不清按钮上的文本,显得很不方便,想要修改背景色所以添加了background字段,但是又不起作用!!! 1.找到values文件夹下面的themes文件 ...

  9. node 基础

    使用 vscore 写 node 的配置文件 { "version": "0.2.0", "configurations": [ { &qu ...

  10. 批处理命令for循环(cmd命令)

    记录一下: https://www.cnblogs.com/Braveliu/p/5081087.html