• 「51Nod


    • 【Update - 2020/4/24】更新了倍增优化部分

    Description

    一个 (1)(N) 的排列 (S)(S) 的近似有序的区间满足如下性质:

    1. (S) 的连续子序列。
    2. 第一个数是最小的。
    3. 最后一个数是最大的。

    例如:(S = {3, 1, 2, 5, 4})(S) 的所有近似有序区间为: ({3}, {1}, {1, 2}, {1, 2, 5}, {2}, {2, 5}, {5}, {4})

    给出 (S),求 (S) 的近似有序区间的数量。

    Hint

    (1le Nle 5 imes 10^4)

    Solution

    这题,我们可以对于数列每一个位置 (i) 分别计算 (i) 为左端点的近似有序区间的个数,最后求和。

    首先,可以明确的是,右端点可能出现的位置必定在 第一个小于第 (i) 个数的位置((operatorname{lower}(i)) 之前。

    然后,先找到 第一个大于第 (i) 个数的位置((operatorname{upper}(i)),然后一直往 (operatorname{upper}) 的方向跳,边跳边使答案自增,超过 (operatorname{lower}(i)) 的时候停止。

    为什么这么跳就可以找到答案?

    题中规定最左边必须最小,那么限定在 (operatorname{lower}(i)) 之前即可,之间的不可能有比第 (i) 个更小的。

    题中又规定最右边必须最大,那么下一个跳的位置不可能是比当前位置小的,并且第一个满足条件的就是 (operatorname{upper})

    为什么这样不会超时?

    显然这个算法会被形如 ({1,2,cdots,N}) 的数据卡成 (O(n^2))不好意思然而数据水。

    如何优化?

    肉眼可见瓶颈在 跳 (operatorname{upper}) 的地方可能会达到一次 (O(n)), 这就是超时的主要原因。因此可以试试优化这一部分。

    这么实现快速的往上跳? 倍增!!

    回忆一下 倍增 LCA 的做法,和这一个差不多。

    首先建树。对于第 (i) 号结点,它的父结点为 (operatorname{upper}(i)) 。如果 (operatorname{upper}) 方向不存在就设成 (n + 1)。最后我们得到了一颗节点数为 (n + 1) 且根为第 (n+1) 号结点的树。

    于是整个跳 (operatorname{upper}) 的过程变成了树上跳祖先——找到深度最小的且结点编号小于 (operatorname{lower}) 的祖先。

    之后预处理父结点表,方法和 倍增 LCA 基本一致: (f(i,j)) 表示结点 (i) 的第 (2^j) 级祖先。然后显然 (f(i,j)=f(f(i,j-1),j-1)),如果不理解可以看看 倍增 LCA 的做法。

    最后统计答案,对于一个结点 (i) ,倍增往上跳,设最终跳到了结点 (k),那么就将答案加上 (operatorname{dep}(i) - operatorname{dep}(i) + 1) 。((operatorname{dep}) 表示结点的深度)

    复杂度是非常优秀的 (O(nlog n))

    Code

    优化前:

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : 51Nod - 1249 近似有序区间
     */
    #include <iostream>
    #include <stack>
    using namespace std;
    
    const int N = 5e4 + 5;
    stack<int> st;
    int lower[N], upper[N], A[N];
    int n;
    
    void calcBound() {
    	while (!st.empty()) st.pop();
    	for (register int i = n; i; i --) {
    		while (!st.empty() && A[st.top()] > A[i]) st.pop();
    		lower[i] = st.empty() ? n + 1 : st.top();
    		st.push(i);
    	}
    	
    	while (!st.empty()) st.pop();
    	for (register int i = n; i; i --) {
    		while (!st.empty() && A[st.top()] < A[i]) st.pop();
    		upper[i] = st.empty() ? n + 1 : st.top();
    		st.push(i);
    	}
    }
    
    int calcPairs() {
    	int ret = n;
    	for (register int i = 1; i <= n; i ++)
    		for (register int j = upper[i]; j < lower[i]; j = upper[j]) ret ++;
    	return ret;
    }
    
    signed main() {
    	cin >> n;
    	for (register int i = 1; i <= n; i ++)
    		cin >> A[i];
    	calcBound();
    	cout << calcPairs() << endl;
    	return 0;
    }
    

    优化后:

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : 51Nod - 1249 近似有序区间
     */
    #include <iostream>
    #include <cstring>
    #include <vector>
    #include <stack>
    using namespace std;
    
    const int N = 5e4 + 5;
    const int LogN = 25;
    
    stack<int> st;
    int lower[N], upper[N];
    int n, A[N];
    
    void calcBound() {
    	while (!st.empty()) st.pop();
    	for (register int i = n; i; i --) {
    		while (!st.empty() && A[st.top()] > A[i]) st.pop();
    		lower[i] = st.empty() ? n + 1 : st.top();
    		st.push(i);
    	}
    	
    	while (!st.empty()) st.pop();
    	for (register int i = n; i; i --) {
    		while (!st.empty() && A[st.top()] < A[i]) st.pop();
    		upper[i] = st.empty() ? n + 1 : st.top();
    		st.push(i);
    	}
    }
    
    vector<int> G[N];
    int f[N][LogN], dep[N];
    
    void buildTable(int rt, int fa) {
    	dep[rt] = dep[fa] + 1, f[rt][0] = fa;
    	
    	for (register int i = 1; (1 << i) <= dep[rt]; i ++)
    		f[rt][i] = f[f[rt][i - 1]][i - 1];
    	
    	for (vector<int>::iterator it = G[rt].begin(); it != G[rt].end(); it ++)
    		buildTable(*it, rt);
    }
    
    void buildTree() {
    	for (register int i = 1; i <= n; i ++)
    		G[upper[i]].push_back(i);
    	buildTable(n + 1, 0);
    }
    
    long long calcPairs() {
    	long long ret = 0ll;
    	for (register int i = 1; i <= n; i ++) {
    		int k = i;
    		for (register int j = LogN - 1; j >= 0; j --) {
    			if (!f[k][j] || f[k][j] >= lower[i]) continue;
    			k = f[k][j];
    		}
    		ret += dep[i] - dep[k] + 1;
    	}
    	return ret;
    }
    
    signed main() {
    	cin >> n;
    	for (register int i = 1; i <= n; i ++) cin >> A[i];
    	calcBound();
    	buildTree();
    	cout << calcPairs() << endl;
    	return 0;
    }
    
    
  • 相关阅读:
    开始逆向objc基础准备(一)简单认识一下arm32,以及与x86汇编指令类比
    构思一个在windows下仿objc基于动画层ui编程的ui引擎
    设计模式学习笔记整理手册
    面向对象的设计原则
    Oracle和Mysql获取uuid的方法对比
    设计模式之解释器模式(行为型)
    设计模式之命令模式(行为型)
    设计模式之中介者模式(行为型)
    设计模式之迭代器模式(行为型)
    Delphi
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/12731293.html
Copyright © 2020-2023  润新知