hdu6759及单调栈原理学习

这是去年杭电多校赛第一场的一道题,题目大意是给出 n n n个机器人的位置和他们的加速度,初始速度都是 0 0 0,问它们可能有多少个能够冲到第一的位置,并列第一不算

  • 简单分析一下这个问题,刚看到这个问题可能有一个想法是加速度最大的那个人一定会走到第一,初始位置最大的那个人也是第一,但中间位置的人怎么处理呢?
  • 按照上述想法,有一种考虑是每次都看谁能够冲到第一,那么这样原来的第一就被取缔了,换一个新的第一,继续如此处理下去,那么这样时间复杂度可能就是 O ( n 2 ) O(n^2) O(n2),这样时间上显然不行
  • 换一种考虑,把每个机器人按照初始位置从大到小排序这个想法应该不难想到,那么如果位置相同,我们应该按照加速度也从大到小,我们现在想通过从前往后依次处理,那么第一个元素现在已经是第一了,考虑使用一个栈来记录曾经达到过第一的元素,栈顶元素表示当前第一位置的元素,我们可能的困惑是如果这样从前往后遍历,要是当前第一的机器人被后面某个机器人在它当第一之前就超过了,那不就出错了吗?解决这个问题的方法就是单调栈的思想,如果后面元素超过当前第一的时间比当前第一当第一的时间还要短,那么这个第一就是假的,他就不能是第一,也就是把它出栈,后面的元素入栈,成为新的第一,然后继续往后走,最后栈里元素就是可能达到第一的元素,还需要考虑唯一性,也就是位置加速度都相等的机器人不算数

单调栈原理

  • 刚才已经把问题分析的很清楚了,单调栈在其中发挥的作用也比较明显,那么什么是单调栈
  • 这个问题似乎并没有什么科学的解释,就是栈的扩展使用,所谓单调递增栈还是单调递减栈好像也没有什么统一的规定,那么我们按照数组升降序这个思路,姑且就这样定义:如果一个单调栈的栈顶元素是栈内最大的元素,那么我们就把它叫做单调递增栈;如果一个单调栈的栈顶元素是栈内最小的元素,那么我们就把它叫做单调递减栈
  • 那其实这个原理部分就结束了,就是如果你发现问题可以用单调栈模型来解决,那就维护一个单调栈,始终保持栈内元素的具有某种单调性,可以保证每个元素最多只出栈入栈两次,这样整个事件复杂度就是 O ( n ) O(n) O(n)
  • 所以综上所述,有难度的地方就在于建模

解决初始问题

  • 根据刚才的分析,根据时间,我们维护一个单调递增栈,首先保持栈内始终有元素,如果发现栈顶机器人当上第一之前就已经被后面的人超过了, 那他就出栈,也就是他不可能贡献答案,至于时间的计算可以根据 s = 1 2 a t 2 s=\frac{1}{2}at^2 s=21at2来,这里注意一下大小关系,还有比较时间大小即可,这样一直进行下去,最后栈内元素都是答案,当然不能包括并列的情况
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double INF = 9999999999.0;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
struct st{
    
    
    int p, a;
    double tim;
    bool operator < (const st &x)const{
    
    
        return p == x.p ? a > x.a : p > x.p;
    }
}s[MAXN];
int dcmp(double a, double b){
    
    
    if(abs(a - b) < eps) return 0;
    return a < b ? -1 : 1;
}
double cal(int top, int now){
    
    
    if(s[top].a != s[now].a){
    
    
        return 1.0 * (s[top].p - s[now].p) / (s[now].a - s[top].a);
    }
    if(s[top].p == s[now].p) return 0.0;
    return INF;
}
int main(){
    
    
    #ifdef LOCAL
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    #endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t, n;
    cin >> t;
    while(t--){
    
    
        cin >> n;
        for(int i=0;i<n;i++){
    
    
            cin >> s[i].p >> s[i].a;
        }sort(s, s + n);
        s[0].tim = INF;
        stack<int> sk;
        map<pair<int, int>, int> mp;
        sk.push(0);
        mp[{
    
    s[0].p, s[0].a}] += 1;
        for(int i=1;i<n;i++){
    
    
            int now = sk.top();
            if(s[i].a < s[now].a) continue;
            if(s[i].p != s[now].p && s[i].a == s[now].a) continue;
            double tim = cal(now, i);
            while(sk.size() > 1 && dcmp(tim, s[now].tim) <= 0){
    
    
                sk.pop();
                now = sk.top();
                tim = cal(now, i);
            }
            s[i].tim = tim;
            sk.push(i);
            mp[{
    
    s[i].p, s[i].a}] += 1;
        }
        int ans = 0;
        while(!sk.empty()){
    
    
            int now = sk.top();
            sk.pop();
            if(mp[{
    
    s[now].p, s[now].a}] == 1) ans += 1;
        }
        cout << ans << '\n';
    }
    return 0;
}

例题

https://www.luogu.com.cn/problem/P5788
https://www.luogu.com.cn/problem/P2866
https://www.luogu.com.cn/problem/P1823

  • 第一题是模板,最常见的模型就是求一组数据中某个元素之后第一个大于它的元素是谁,考虑维护一个单调递减栈,栈顶元素是栈内数据中最小的,如果当前元素大于栈顶元素,那么更新答案,并把栈顶元素弹出,当前元素入栈,循环即可
  • 第二题也是常见模型,给你 n n n头牛,从左到右依次是从排尾到排头,定义如果 i i i牛能够看到 j j j牛,那么要求是 i i i身高必须要严格大于 [ i + 1 , j ] [i+1,j] [i+1,j]这些牛,考虑维护一个单调递减栈,栈顶元素表示当前身高最低的元素,他会对他之前的栈内元素产生贡献,如果当前元素比栈顶元素大,那么就弹出栈顶元素,知道栈空或者找到一个更大的数。因为考虑到如果存在一个 k k k,满足 i < k < j i\lt k\lt j i<k<j,但是身高大于 i i i,那么他就会隔断 i i i j j j之间的联系,这符合单调栈模型,
  • 第三题看起来似乎和第二题差不多,但这里对于能够互相看见给出的定义是他们之间的人身高不能超过他们中的一个,也就是他们之间的人的身高要小于或者等于身高较矮的那一个。我们仍然考虑维护一个单调栈,那么单调性应该怎么样呢?因为中间可以有相等情况,所以应该是单调不增栈,具体实现如下,如果发现一个元素比当前栈顶元素大,那么这个元素对于答案的贡献应该是多少呢?这时候栈顶元素应该出栈了,可以这样考虑,每出栈一个元素就累计一个答案,然后再加上之前这个元素的数量;如果发现一个元素和栈顶元素相等,那么就加上栈顶元素出现次数,如果这之前还有元素,那么答案再+1。上述过程如果有问题纸上稍微推一推即可推出
//P5788
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int MAXN = 3e6 + 100;
const double eps = 1e-6;
int a[MAXN], ans[MAXN];
stack<int> s;
int main(){
    
    
	#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	#endif
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n;
	cin >> n;
	for(int i=1;i<=n;i++){
    
    
		cin >> a[i];
		while(!s.empty() && a[s.top()] < a[i]){
    
    
			ans[s.top()] = i;
			s.pop();
		}
		s.push(i);
	}
	for(int i=1;i<=n;i++){
    
    
		cout << ans[i] << ' ';
	}
	return 0;
}
//P2866
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int a[MAXN];
stack<int> s;
int main(){
    
    
	#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	#endif
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n;
	cin >> n;
	ll ans = 0;
	for(int i=1;i<=n;i++){
    
    
		cin >> a[i];
		while(!s.empty() && a[s.top()] <= a[i]){
    
    
			s.pop();
		}
		ans += s.size();
		s.push(i);
	}
	cout << ans << '\n';
	return 0;
}
//P1823
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <iomanip>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int MAXN = 1e6 + 100;
const double eps = 1e-6;
int a[MAXN];
stack<int> s;
map<int, int> mp;
int main(){
    
    
	#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
	#endif
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int n;
	cin >> n;
	ll ans = 0;
	for(int i=1;i<=n;i++){
    
    
		cin >> a[i];
		while(!s.empty() && a[s.top()] < a[i]){
    
    
			mp[a[s.top()]] -= 1;
			s.pop();
			ans += 1;
		}
		if(!s.empty()){
    
    
			if(a[i] == a[s.top()]){
    
    
				ans += mp[a[i]];
				if(s.size() != mp[a[i]]) ans += 1;
			}else{
    
    
				ans += 1;
			}
		}
		s.push(i);
		mp[a[i]] += 1;
	}
	cout << ans << '\n';
	return 0;
}
  • 欢迎留言交流

猜你喜欢

转载自blog.csdn.net/roadtohacker/article/details/120301924