zoukankan      html  css  js  c++  java
  • [JZOJ3233] 照片

    题目

    题目大意

    有一个(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+1geq s_r)(s_r-1geq 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
    ");
    	else
    		printf("%d
    ",f[n+1]-1);
    	return 0;
    }
    

    总结

    真是一道单调队列的好题。
    当然,如果是比赛,我肯定会选择打线段树的。

  • 相关阅读:
    rabbitmq使用__python客户端(消息接收者)
    Rabbitmq Exchange Type 说明
    rabbitmq使用__php客户端(消息发送者)
    rabbitmq使用__python客户端(消息发送者)
    安装python的rabbitmq扩展库
    安装rabbitmq服务器端
    课程1:历经5年锤炼(史上最适合初学者入门的Java基础视频)视频列表
    新笔记本JAVA环境配置,MySQL,navicat 安装
    局域网介质访问控制方法
    SQL Server 2008之DMF
  • 原文地址:https://www.cnblogs.com/jz-597/p/11179325.html
Copyright © 2011-2022 走看看