题目

题目大意

有一个\(01\)序列。给你一堆区间,每个区间中有且仅有一个\(1\)点。

问最多的\(1\)点个数。


思考历程

感觉这题特别经典,似乎在哪里见过,又好像没有见过。

一开始朝贪心方面想……想不出来……

后来想DP,还是想不出来……

直到WHH大爷跑过来兴奋地说:不就是个差分约束吗!

于是我心态崩了,去搜题解……

然而搜到的全是DP……

理解了一下DP的方法,虽然理解,但是极度不熟练……

可是这题对代码能力的考验又很强……我本想自己打出极其恶心的方法,但后来还是仔细看了看题解(还有)标程。

几乎是对着标打出来了……

心态崩了……


正解

先说DP。

设\(f_i\)表示选\(i\)的最多的\(1\)点数目。

方程为\(f_i=\max f_j +1\)

现在问题是\(j\)的范围怎么求。

这就很考验人的分析能力了——从两个方面考虑:

  1. 唯一性:所有包括\(i\)的区间内都不能再选别的。这样可以求出右边界\(R_i\),为包含\(i\)的区间的最小左边界\(-1\)
  2. 必要性:所有不包括\(i\)的区间(\(i\)之前的)中都必须要有一个选。这样可以求出右边界\(L_i\),为不包含\(i\)的区间的最大左边界

这样,求出\(L_i\)和\(R_i\),就可以转移了。

如果不刻意追求代码的完美,其实这个时候用线段树来辅助转移就好了,简单粗暴(如果是比赛,我肯定就这么打了)。

但是实际上可以优化。

首先可以证明出一个结论:\(L\)和\(R\)都是递增的。

如果\(L_{i-1}>L_i\),由于包含了\(i\)的区间也会包含\(i-1\)(除非区间左边界为\(i\),但显然这种情况下\(L{i-1}\leq L_i\)),\(L_{i-1}\)为包含\(i-1\)区间的最小左边界\(-1\),所以\(L_{i-1}\)可以被更新,所以不成立。

如果\(R_{i-1}>R_i\),由于不包含\(i-1\)的区间也不包含\(i\),\(R_i\)为不包含\(i\)区间的最大右边界,所以\(R_i\)可以被更新,所以不成立。

既然都是递增的,那就可以很愉快地单调队列DP了……

有了各种单调的性质,代码也可以简单很多,也不需要打线段树或堆了……

然而……如果是在比赛,想这些的时间还不如用来打线段树呢……

还有一种方法是差分约束。

假如我们做一遍前缀和,对于区间\([l,r]\),就会有\(s_l+1=s_r\),相当于\(s_l+1\geq s_r\)且\(s_r-1\geq s_l\)。还有,将每个左右端点排个序记为\(x_i\),那么还有\(x_{i-1}\leq x_i\)

差分约束即可……当然我没有打过,所以纯属瞎哔哔……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200010
int n,m;
struct Section{
int l,r;
} s[N];
inline bool cmps(const Section &a,const Section &b){
return a.l<b.l || a.l==b.l && a.r<b.r;
}
int L[N],R[N];
int q[N],head,tail;
int f[N];
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n+1;++i)
R[i]=i-1;
for (int i=1;i<=m;++i){
scanf("%d%d",&s[i].l,&s[i].r);
L[s[i].r+1]=max(L[s[i].r+1],s[i].l);
R[s[i].r]=min(R[s[i].r],s[i].l-1);
}
for (int i=n;i>=1;--i)
R[i]=min(R[i],R[i+1]);
for (int i=2;i<=n+1;++i)
L[i]=max(L[i],L[i-1]);
memset(f,127,sizeof f);
f[0]=0;
for (int i=1,j=0;i<=n+1;++i){
for (;j<=R[i];++j){
if (f[j]==0x7f7f7f7f)
continue;
while (head<=tail && f[q[tail]]<f[j])
tail--;
q[++tail]=j;
}
while (head<=tail && q[head]<L[i])
head++;
if (head<=tail)
f[i]=f[q[head]]+1;
}
if (f[n+1]==0x7f7f7f7f)
printf("-1\n");
else
printf("%d\n",f[n+1]-1);
return 0;
}

总结

真是一道单调队列的好题。

当然,如果是比赛,我肯定会选择打线段树的。

最新文章

  1. 自动添加Linux登录账户,并授予sudo权限
  2. 我的LESS编译方案
  3. MySQL的数据类型
  4. JAVA IO 字节流与字符流
  5. 使用CSS和jQuery实现tab页
  6. 记2012-2013年一路的Windows Phone历程
  7. echarts在360中以及IE8浏览器不兼容:解决方案
  8. [ CodeVS冲杯之路 ] P3955
  9. c 语言时间的输出和比较
  10. CSDN总结的面试中的十大算法
  11. java 发送邮件 email相关操作代码测试,生成复杂格式邮件,发送邮件相关操作
  12. Python Revisited Day 06 (面向对象程序设计)
  13. MyEclipse has detected that less than 5% of
  14. node.js 高级功能
  15. Unix环境高级编程-阻塞访问原理——等待队列
  16. cmd运行java程序---路径容易出错的问题
  17. 乐视4.14硬件免费日de用户体验
  18. c++的各种类型转换方式
  19. 顶点与UV
  20. 第二百八十七节,MySQL数据库-条件语句、循环语句、动态执行SQL语句

热门文章

  1. Selenium3 + Python3自动化测试系列七——多窗口切换
  2. 提升方法(boosting)详解
  3. Mysql 查询表中某字段的重复值,删除重复值保留id最小的数据
  4. jdk8中map新增的merge方法介绍
  5. wireshark 分析 TCP 请求(转)
  6. java 面试2019
  7. sqlmap 使用方法及实例
  8. sql 左连接与右连接
  9. Flink开发-IDEA scala开发环境搭建
  10. select 可输入的下拉框