BZOJ5259/洛谷P4747: [Cerc2017]区间

2019.8.5 [HZOI]NOIP模拟测试13 C.优美序列

思维好题,然而当成NOIP模拟题↑真的好吗...

洛谷和BZOJ都有,就不设密码了。

首先,手玩样例可以发现满足条件的区间是不满足单调性的,所以二分左右端点、单调队列、双指针什么的就不可能了。

然后不会了...

不难看出,一段满足要求的区间[L,R],符合\(val_{max}-val_{min}=R-L\),val是数值。

50pts暴力:对val建st表,每次询问枚举序列的子区间,用st表\(O(1)\)判断是否可行,复杂度\(O(n^2m)\)。考试数据可能弱化过,洛谷和BZOJ上应该水不到50pts。

Code:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e5+5;
  4. int n,m,mx,mn,a[N],Lg[N],f[22][N],g[22][N];
  5. inline void In(int &num){
  6. char c=getchar();
  7. for(num=0;!isdigit(c);c=getchar());
  8. for(;isdigit(c);num=num*10+c-48,c=getchar());
  9. }
  10. void st_init(){
  11. Lg[0]=-1;
  12. for(int i=1;i<=n;++i) f[0][i]=g[0][i]=a[i],Lg[i]=Lg[i>>1]+1;
  13. for(int i=1;i<=20;++i)
  14. for(int j=1;j+(1<<i)-1<=n;++j)
  15. f[i][j]=max(f[i-1][j],f[i-1][j+(1<<(i-1))]),
  16. g[i][j]=min(g[i-1][j],g[i-1][j+(1<<(i-1))]);
  17. }
  18. void query(int l,int r){
  19. int d=Lg[r-l+1];
  20. mx=max(f[d][l],f[d][r-(1<<d)+1]);
  21. mn=min(g[d][l],g[d][r-(1<<d)+1]);
  22. }
  23. int main(){
  24. In(n);
  25. for(int i=1;i<=n;++i) In(a[i]);
  26. st_init();
  27. In(m);
  28. for(int i=1,l,r,Mx,Mn;i<=m;++i){
  29. In(l);In(r);
  30. query(l,r);
  31. Mx=mx;Mn=mn;
  32. for(int j=r-l+1;j<=n;++j){
  33. for(int k=1;k+j-1<=n;++k){
  34. query(k,k+j-1);
  35. if(mx-mn==j-1&&mx>=Mx&&mn<=Mn){
  36. printf("%d %d\n",k,k+j-1);
  37. goto nxt;
  38. }
  39. }
  40. }
  41. nxt:;
  42. }
  43. return 0;
  44. }

92pts暴力:模拟找答案的过程。记pos[i]为i在原序列中的下标,读入时pos[val[i]]=i。对val和pos数组建st表。

例如样例一,如果询问[5,7]的数(6 4 2),在val的st表中查到下标在[5,7]之间的最小值是2、最大值是6。所以26这五个数都要出现。然后在pos的st表中查数字26在序列中的出现位置:3出现在第一位,2出现在最后一位,所以整个序列都要选。此时序列中最大值是7,最小值是1,序列为[1,7],恰好符合,得到答案。

模拟此过程即可,复杂度未知。92pts还是指考试的弱数据,需要轻度卡常,为了可读性只放一份未卡常的。

Code:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e5+5;
  4. int n,m,mx,mn,Mx,Mn,a[N],pos[N],Lg[N],f[22][N],g[22][N],t[22][N],s[22][N];
  5. inline void In(int &num){
  6. char c=getchar();
  7. for(num=0;!isdigit(c);c=getchar());
  8. for(;isdigit(c);num=num*10+c-48,c=getchar());
  9. }
  10. void st_init(){
  11. Lg[0]=-1;
  12. for(int i=1;i<=n;++i) f[0][i]=g[0][i]=a[i],t[0][i]=s[0][i]=pos[i],Lg[i]=Lg[i>>1]+1;
  13. for(int i=1;i<=20;++i)
  14. for(int j=1;j+(1<<i)-1<=n;++j)
  15. f[i][j]=max(f[i-1][j],f[i-1][j+(1<<(i-1))]),
  16. g[i][j]=min(g[i-1][j],g[i-1][j+(1<<(i-1))]),
  17. t[i][j]=max(t[i-1][j],t[i-1][j+(1<<(i-1))]),
  18. s[i][j]=min(s[i-1][j],s[i-1][j+(1<<(i-1))]);
  19. }
  20. void query_val(int l,int r){
  21. int d=Lg[r-l+1];
  22. mx=max(f[d][l],f[d][r-(1<<d)+1]);
  23. mn=min(g[d][l],g[d][r-(1<<d)+1]);
  24. }
  25. void query_pos(int l,int r){
  26. int d=Lg[r-l+1];
  27. Mx=max(t[d][l],t[d][r-(1<<d)+1]);
  28. Mn=min(s[d][l],s[d][r-(1<<d)+1]);
  29. }
  30. int main(){
  31. In(n);
  32. for(int i=1;i<=n;++i) In(a[i]),pos[a[i]]=i;
  33. st_init();
  34. In(m);
  35. for(int i=1,l,r;i<=m;++i){
  36. In(l);In(r);
  37. query_val(l,r);
  38. query_pos(mn,mx);
  39. while(Mx-Mn!=mx-mn){
  40. query_val(Mn,Mx);
  41. query_pos(mn,mx);
  42. }
  43. printf("%d %d\n",Mn,Mx);
  44. }
  45. return 0;
  46. }

100pts:

考试的题解

分治法,离线处理。假设现在处理的询问都包含在[L,R] 中,设mid=(L+R)/2。然后将包含在[L,mid],[mid+1,R] 的区间分治处理。剩下的就是包含[mid,mid+1]的询问,然后找出包含[mid,mid+1]的所有优美区间,用这些优美区间更新询问的答案。

时间复杂度\(O(n(logn)^2)\)。

序列分治不太会,咕了。

介绍两种思路。

方法一:

扫描线+线段树

洛谷题解区的dalao想到的。

这个思路不太容易理解,并且我的表达能力确实有限,如果不看代码下面的话应该是看不懂的,建议去luogu题解区看下dalao解释,并结合代码理解。

考虑如何判断一个区间是连续段,当且仅当区间内\((x,x+1)\)的对数为\(r−l\)。

设区间\([l,r]\)内\((x,x+1)\)的对数为\(c(l,r)\)。

我们可以枚举右端点r,用线段树维护\(l+c(l,r)\)。可以发现合法仅当\(l+c(l,r)=r\),并且\(l+c(l,r)\)最大值为r,所以只需要维护最大值以及最大值的位置就可以了。

实现的时候把所有询问离线,枚举到了询问的r端点就把询问丢进一个优先队列里面,以询问的l端点为关键字,堆顶是l最大的。每次如果能找到答案就pop,否则就break,因为查询的是[1,l]的最大值,l越大一定越容易找到答案。

简单说一下这样做的正确性:

对于询问[ql,qr],我们从qr开始枚举答案的右端点R,找到第一个能覆盖[ql,qr]的L,区间[L,R]就是答案。

可以反证:



假如我们枚举到R1,找到区间[L1,R1]是好区间,作为答案,但答案应该是[L2,R2]。那么[L1,R1]和[L2,R2]都是好区间。实际上[L2,R1]也是好区间,因为如果[L2,R1]的数不连续就不可能成为两个好区间的交集。于是我们枚举到R1得到的答案实际上是L2,[L2,R1]正是最优解。

Code:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e5+5;
  4. typedef pair<int,int> Node;
  5. int n,m,Mx,Mx_pos,a[N],pos[N];
  6. Node ans[N];
  7. priority_queue<Node> heap;
  8. vector<Node> que[N];
  9. struct tree_node{
  10. int l,r,mx,pos,tag;
  11. #define l(p) (node[p].l)
  12. #define r(p) (node[p].r)
  13. #define mx(p) (node[p].mx)
  14. #define pos(p) (node[p].pos)
  15. #define tag(p) (node[p].tag)
  16. #define ls(p) (p<<1)
  17. #define rs(p) (p<<1|1)
  18. #define mid ((l(p)+r(p))>>1)
  19. }node[N<<2];
  20. void pup(int p){
  21. mx(p)=max(mx(ls(p)),mx(rs(p)));
  22. pos(p)=mx(ls(p))>mx(rs(p))?pos(ls(p)):pos(rs(p));
  23. }
  24. void build(int p,int l,int r){
  25. l(p)=l;r(p)=r;
  26. if(l==r) return (void) (mx(p)=pos(p)=l);
  27. build(ls(p),l,mid);
  28. build(rs(p),mid+1,r);
  29. pup(p);
  30. }
  31. void pdown(int p){
  32. if(tag(p)){
  33. mx(ls(p))+=tag(p);tag(ls(p))+=tag(p);
  34. mx(rs(p))+=tag(p);tag(rs(p))+=tag(p);
  35. tag(p)=0;
  36. }
  37. }
  38. void modify(int p,int L,int R){
  39. if(L<=l(p)&&r(p)<=R) return (void) (++mx(p),++tag(p));
  40. pdown(p);
  41. if(L<=mid) modify(ls(p),L,R);
  42. if(R>mid) modify(rs(p),L,R);
  43. pup(p);
  44. }
  45. void query(int p,int L,int R){
  46. if(L<=l(p)&&r(p)<=R){
  47. if(mx(p)>=Mx) Mx=mx(p),Mx_pos=pos(p);
  48. return;
  49. }
  50. pdown(p);
  51. if(L<=mid) query(ls(p),L,R);
  52. if(R>mid) query(rs(p),L,R);
  53. }
  54. bool check(const Node &w,int R){
  55. Mx=0;
  56. query(1,1,w.first);
  57. if(Mx==R) {ans[w.second]=make_pair(Mx_pos,R);return true;}
  58. return false;
  59. }
  60. int main(){
  61. scanf("%d",&n);
  62. for(int i=1;i<=n;++i) scanf("%d",&a[i]);
  63. build(1,1,n);
  64. scanf("%d",&m);
  65. for(int i=1,l,r;i<=m;++i){
  66. scanf("%d%d",&l,&r);
  67. que[r].push_back(make_pair(l,i));
  68. }
  69. for(int i=1;i<=n;++i){
  70. pos[a[i]]=i;
  71. if(pos[a[i]-1]) modify(1,1,pos[a[i]-1]);
  72. if(pos[a[i]+1]) modify(1,1,pos[a[i]+1]);
  73. for(unsigned j=0;j<que[i].size();++j) heap.push(que[i][j]);
  74. while(!heap.empty()){
  75. if(check(heap.top(),i)) heap.pop();
  76. else break;
  77. }
  78. }
  79. for(int i=1;i<=m;++i) printf("%d %d\n",ans[i].first,ans[i].second);
  80. return 0;
  81. }

方法二:

线段树优化建图+tarjan缩点

可以去dky博客看解释。

Code:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=2e6+5,inf=0x3f3f3f3f;
  4. int n,m,a[N];
  5. struct Graph{
  6. int Top,head[N],ver[N],nxt[N];
  7. inline void add(int u,int v){
  8. ver[++Top]=v;
  9. nxt[Top]=head[u];
  10. head[u]=Top;
  11. }
  12. }G1,G2;
  13. struct Node{
  14. int l,r;
  15. inline Node(int l=inf,int r=-inf):l(l),r(r) {}
  16. inline Node operator + (const Node &b)const{
  17. return Node(min(l,b.l),max(r,b.r));
  18. }
  19. }t1[N],t2[N];
  20. struct SegmentTree{
  21. Node t[N];
  22. #define mid ((l+r)>>1)
  23. #define ls(p) (p<<1)
  24. #define rs(p) (p<<1|1)
  25. void modify(int p,int l,int r,int pos,const Node &val){
  26. if(l==r) return (void) (t[p]=val);
  27. pos<=mid?modify(ls(p),l,mid,pos,val):modify(rs(p),mid+1,r,pos,val);
  28. t[p]=t[ls(p)]+t[rs(p)];
  29. }
  30. Node query(int p,int l,int r,int L,int R){
  31. if(L<=l&&r<=R) return t[p];
  32. if(L<=mid&&R>mid) return query(ls(p),l,mid,L,R)+query(rs(p),mid+1,r,L,R);
  33. else if(L<=mid) return query(ls(p),l,mid,L,R);
  34. else return query(rs(p),mid+1,r,L,R);
  35. }
  36. }seg[2];
  37. int rt,tot,ls[N],rs[N];
  38. void build_graph(int &p,int l,int r){
  39. if(l==r) return (void) (p=l);
  40. p=++tot;
  41. build_graph(ls[p],l,mid);
  42. build_graph(rs[p],mid+1,r);
  43. G1.add(p,ls[p]);
  44. G1.add(p,rs[p]);
  45. }
  46. void Link(int p,int l,int r,int u,int L,int R){
  47. if(L<=l&&r<=R) return G1.add(u,p);
  48. if(L<=mid) Link(ls[p],l,mid,u,L,R);
  49. if(R>mid) Link(rs[p],mid+1,r,u,L,R);
  50. }
  51. int tp,tim,scc_num,dfn[N],low[N],st[N],c[N];
  52. void tarjan(int u){
  53. st[++tp]=u;
  54. dfn[u]=low[u]=++tim;
  55. for(int i=G1.head[u];i;i=G1.nxt[i]){
  56. int v=G1.ver[i];
  57. if(!dfn[v]){
  58. tarjan(v);
  59. low[u]=min(low[u],low[v]);
  60. }
  61. else if(!c[v]) low[u]=min(low[u],dfn[v]);
  62. }
  63. if(low[u]==dfn[u]){
  64. ++scc_num;
  65. int y;
  66. do{
  67. y=st[tp--];
  68. c[y]=scc_num;
  69. }while(y!=u);
  70. }
  71. }
  72. bool vis[N];
  73. void dfs(int u){
  74. if(vis[u]) return;
  75. vis[u]=true;
  76. for(int i=G2.head[u];i;i=G2.nxt[i]){
  77. int v=G2.ver[i];
  78. dfs(v);
  79. t2[u]=t2[u]+t2[v];
  80. }
  81. }
  82. int main(){
  83. scanf("%d",&n);
  84. tot=n;
  85. build_graph(rt,1,n);
  86. for(int i=1;i<=n;++i) scanf("%d",&a[i]);
  87. for(int i=1;i<=n;++i) seg[0].modify(1,1,n,a[i],Node(i,i));
  88. for(int i=2;i<=n;++i){
  89. int x=min(a[i-1],a[i]),y=max(a[i-1],a[i]);
  90. t1[i]=seg[0].query(1,1,n,x,y);
  91. Link(rt,1,n,i,t1[i].l+1,t1[i].r);//i表示[i-1,i]两个数
  92. }
  93. for(int i=1;i<=tot;++i) if(!dfn[i]) tarjan(i);
  94. for(int u=1;u<=tot;++u){
  95. for(int i=G1.head[u];i;i=G1.nxt[i]){
  96. int v=G1.ver[i];
  97. if(c[u]!=c[v]) G2.add(c[u],c[v]);
  98. }
  99. }
  100. for(int i=1;i<=tot;++i) t2[c[i]]=t2[c[i]]+t1[i];
  101. for(int i=1;i<=scc_num;++i) dfs(i);
  102. for(int i=2;i<=n;++i) seg[1].modify(1,1,n,i,t2[c[i]]);
  103. scanf("%d",&m);
  104. for(int i=1,l,r;i<=m;++i){
  105. scanf("%d%d",&l,&r);
  106. if(l==r) printf("%d %d\n",l,r);
  107. else{
  108. Node ans=seg[1].query(1,1,n,l+1,r);
  109. printf("%d %d\n",ans.l,ans.r);
  110. }
  111. }
  112. return 0;
  113. }

BZOJ5259/洛谷P4747: [Cerc2017]区间的更多相关文章

  1. 洛谷 P4747 [CERC2017]Intrinsic Interval 线段树维护连续区间

    题目描述 题目传送门 分析 考虑对于 \([l,r]\),如何求出包住它的长度最短的好区间 做法就是用一个指针从 \(r\) 向右扫,每次查询以当前指针为右端点的最短的能包住 \([l,r]\) 的好 ...

  2. 洛谷 1063 dp 区间dp

    洛谷 1063 dp 区间dp 感觉做完这道提高组T1的题之后,受到了深深的碾压,,最近各种不在状态.. 初看这道题,不难发现它具有区间可并性,即(i, j)的最大值可以由(i, k) 与 (k+1, ...

  3. 洛谷P1712 [NOI2016]区间 尺取法+线段树+离散化

    洛谷P1712 [NOI2016]区间 noi2016第一题(大概是签到题吧,可我还是不会) 链接在这里 题面可以看链接: 先看题意 这么大的l,r,先来个离散化 很容易,我们可以想到一个结论 假设一 ...

  4. 洛谷 P1890 gcd区间

    P1890 gcd区间 题目提供者 洛谷OnlineJudge 标签 数论(数学相关) 难度 普及/提高- 题目描述 给定一行n个正整数a[1]..a[n]. m次询问,每次询问给定一个区间[L,R] ...

  5. 洛谷P2879 [USACO07JAN]区间统计Tallest Cow

    To 洛谷.2879 区间统计 题目描述 FJ's N (1 ≤ N ≤ 10,000) cows conveniently indexed 1..N are standing in a line. ...

  6. 洛谷P2434 [SDOI2005]区间

    题目描述 现给定n个闭区间[ai, bi],1<=i<=n.这些区间的并可以表示为一些不相交的闭区间的并.你的任务就是在这些表示方式中找出包含最少区间的方案.你的输出应该按照区间的升序排列 ...

  7. 洛谷1890 gcd区间

    题目描述 给定一行n个正整数a[1]..a[n].m次询问,每次询问给定一个区间[L,R],输出a[L]..a[R]的最大公因数. 输入输出格式 输入格式: 第一行两个整数n,m.第二行n个整数表示a ...

  8. 洛谷P1890 gcd区间

    题目描述 给定一行n个正整数a[1]..a[n]. m次询问,每次询问给定一个区间[L,R],输出a[L]..a[R]的最大公因数. 输入输出格式 输入格式: 第一行两个整数n,m. 第二行n个整数表 ...

  9. 洛谷——P1890 gcd区间

    P1890 gcd区间 题目描述 给定一行n个正整数a[1]..a[n]. m次询问,每次询问给定一个区间[L,R],输出a[L]..a[R]的最大公因数. 输入输出格式 输入格式: 第一行两个整数n ...

随机推荐

  1. 2018-8-14-Resharper-如何把类里的类移动到其他文件

    title author date CreateTime categories Resharper 如何把类里的类移动到其他文件 lindexi 2018-08-14 17:34:39 +0800 2 ...

  2. php mkdir 777失败

    参考网址:https://www.cnblogs.com/52php/p/5660079.html 在linux系统中在创建文件/文件夹时有一个默认权限,此权限受 umask 设置影响,在/etc/b ...

  3. mysql sum() 求和函数的用法

    查询在record表中 name=? 的 money 加起来的值使用聚和函数 sum() 求和select sum(money) from record t where t.name = ?另外:co ...

  4. 请问“javascript:;”是什么意思?

    请问“javascript:;”是什么意思?   最佳答案   就是 执行一段 空白JAVASCRIPT语句 并且返回的也是空或者false值..把 javascript:; 加在超级链接上 就可以防 ...

  5. JPinyin繁体相互转换

    // 用正则表达式"[\u4e00-\u9fa5]"匹配 字符串 Scanner sc =new Scanner(System.in);System.out.println(&qu ...

  6. java项目小手册

    集合了一些常用的小片段 1. 字符串有整型的相互转换 Java代码 String a = String.valueOf(2); //integer to numeric string int i = ...

  7. itextsharp 1.0

    1 效果图 2.代码 引用组件: using iTextSharp.text;using iTextSharp.text.pdf;using System;using System.Data;usin ...

  8. VMware workstation12安装苹果虚拟机

    一.前言--准备工作 在win10上安装Mac虚拟机,既是费劲又是费内存的活儿 1.安装Vmware 2.下载MacOS的镜像:自行百度下载 3. unlocker的下载地址:http://downl ...

  9. LintCode_175 翻转二叉树

    题目 翻转一棵二叉树 您在真实的面试中是否遇到过这个题? Yes 样例 1 1 / \ / \ 2 3 => 3 2 / \ 4 4 和前序遍历代码很相似从叶子节点依次翻转递归到根节点C++代码 ...

  10. HBuilderX生成本地打包App资源

    http://ask.dcloud.net.cn/question/60254 概要 在HBuilderX中开发的应用可以提交到云端打包生成apk(Android平台)和ipa(iOS平台).如果本地 ...