• [LOJ10121] 与众不同


    题目类型:(DP)+(RMQ)

    传送门:>Here<

    题意:给定一个长度为(N)的序列,并给出(M)次询问。询问区间([L,R])内的最长完美序列。所谓完美序列就是指连续的且内部元素不重复的序列

    解题思路

    暴力做法:对于每一次询问,用一个队列维护,(O(n))给出答案。因此复杂度是(O(nm))

    考虑能否求出以每个点为结尾的最长完美序列长度(f[i]),以及起始位置(s[i])。假设我们已经维护好了一个数组(lst[i])表示数值(a[i])上一次出现的位置。那么(s[i])可以满足转移方程$$s[i]=Max{s[i-1],lst[i]+1}$$其中,(s[i-1])表示以(i-1)为结尾的最长完美序列,然而随着元素(i)的加入,有可能在(s[i-1])之后存在(a[i]),为了避免这种情况,增加(lst[i]+1)为底线。

    (lst)数组并没有简单的方法来维护,那么既然不能(O(n^2)),那么开桶好了(记得负数,(+ad)

    求出(s[i])以后,很明显有$$f[i]=i-s[i]+1$$然后考虑如何应对询问。一个很本能的反应是求出(Max{f[l]..f[r]})。然而这不仅慢,也不正确——用(f[i])的前提条件必须是(s[i] geq l)。因此每个点的答案应该是(Min{f[i],i-L+1})

    然而这样的话复杂度依然是(O(nm)),一种思路是进行优化,用(RMQ)(O(1))解答。然而我们没有办法迅速生成所有的(i-L+1)。但是不是所有的点都需要考虑(i-L+1)?很明显不是,如果一个点的(s[i] geq L)就不需要考虑了。

    (s[i])的值有什么特点?容易发现(s[i])是单调不下降的。(不然暴力中,我们是如何用队列进行维护的?)。新加进来的一个点只会导致起始点不动或后移。

    因此回到询问中来,所有(s[i]geq L)的点一定都分布在该区间的右侧。因此我们可以二分来找到这个分界点,在分界点的右侧做(RMQ)就完全没有任何问题了,我们只需要考虑(f[i])

    同样,分布在左侧的节点只需要考虑到(L)的距离了。最大的一定是左侧的最右侧的那个点

    因此我们先预处理出(s[i]),复杂度(O(n))。然后倍增预处理(O(nlogn)),对于每次询问需要二分((mlogn))。因此整道题的复杂度是(O(n+(n+m)logn))

    反思

    做出本题的关键在于想到维护(s[i]),并且看出(s[i])是单调不下降的。而这两点恰好都在暴力中体现了。因此这告诉我们,正解的很多思路都是来源于暴力的。

    Code

    注意如果二分全都不能满足,那么应该返回(r+1)

    /*By DennyQi 2018*/
    #include <cstdio>
    #include <queue>
    #include <cstring>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    typedef long long ll;
    const int MAXN = 200010;
    const int AD = 1000000;
    const int INF = 1061109567;
    inline int Max(const int a, const int b){ return (a > b) ? a : b; }
    inline int Min(const int a, const int b){ return (a < b) ? a : b; }
    inline int read(){
        int x = 0; int w = 1; register char c = getchar();
        for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
        if(c == '-') w = -1, c = getchar();
        for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
    }
    int N,M,l,r,m;
    int a[MAXN],s[MAXN],lst[MAXN],b[2*AD+100],f[MAXN],st[MAXN][20];
    inline int BinarySearch(int l, int r){
    	int L = l, R = r, Mid, Ans(r+1);
    	while(L <= R){
    		Mid = (L + R) / 2;
    		if(s[Mid] < l){
    			L = Mid + 1;
    		}
    		else{
    			R = Mid - 1;
    			Ans = Mid;
    		}
    	}
    	return Ans;
    }
    inline int RMQ(int l, int r){
    	if(l>r) return -1;
    	int k = log(r-l+1) / log(2);
    	return Max(st[l][k], st[r-(1<<k)+1][k]);
    }
    int main(){
    //	freopen(".in","r",stdin);
    	N = read(), M = read();
    	for(int i = 1; i <= N; ++i){
    		a[i] = read();
    		lst[i] = b[a[i]+AD];
    		b[a[i]+AD] = i;
    	}
    	s[1] = 1;
    	for(int i = 2; i <= N; ++i){
    		s[i] = Max(lst[i]+1, s[i-1]);
    		f[i] = i-s[i]+1;
    		st[i][0] = f[i];
    	}
    	for(int j = 1; (1<<j) <= N; ++j){
    		for(int i = 1; i+(1<<j)-1 <= N; ++i){
    			st[i][j] = Max(st[i][j-1], st[i+(1<<(j-1))][j-1]);
    		}
    	}
    	while(M--){
    		l = read()+1, r = read()+1;
    		m = BinarySearch(l,r);
    		printf("%d
    ", Max(m-1-l+1, RMQ(m,r)));
    	}
    	return 0;
    }
    
  • 相关阅读:
    (转)$.extend()方法和(function($){...})(jQuery)详解
    (转)JSONObject与JSONArray的使用
    (转)java中对集合对象list的几种循环访问总结
    ibatis 参数和结果的映射处理
    Could not calculate build plan
    maven项目工程报错:cannot be resolved to a type
    CronTrigger中cron表达式使用
    ex:0602-169 遇到不完整或无效的多字节字符,转换失败
    UseParNewGC和UseParallelGC的区别
    java gc日志详解
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9862352.html
Copyright © 2020-2023  润新知