《算法竞赛进阶指南》——栈,队列总结

火车进站

实际上就是一个卡特兰数,但是总算知道了公式是怎么推导出来的了。

卡特兰数以前知道的公式好像是递推推出来的:
f ( n ) = f ( 1 ) ∗ f ( n − 1 ) + f ( 2 ) ∗ f ( n − 2 ) . . . + f ( n − 2 ) ∗ f ( 2 ) + f ( n − 1 ) ∗ f ( 1 ) f(n) =f(1)*f(n-1)+f(2)*f(n-2)...+f(n-2) * f(2) + f(n-1) * f(1) f(n)=f(1)f(n1)+f(2)f(n2)...+f(n2)f(2)+f(n1)f(1)
然后它可以用来计算下类问题:
针对每个步骤有两种状态,且有一种状态的数量始终大于等于另一种,且最终两种状态数量相等。
然后比较常用的公式是:
在这里插入图片描述

蚯蚓

题目链接

蚯蚓

解析:

Δ 来储存偏移量(长度增加量),队列中储存相对长度。每次挑选出最长的一段,加上Δ,切割完以后,就把切割后的长度减去一个Δ+q后放入队列中,Δ+=q(因为切割之后的不会增加长度,相对其它的蚯蚓减少了q)。
但是这样会超时。

先来一段证明

设x1 > x2
x1切割后
x 3 x_3 x3= ⌊ x 1 ∗ p ⌋ \lfloor x _1 *p \rfloor x1p;
x 4 x_4 x4= x 1 x_1 x1- ⌊ x 1 ∗ p ⌋ \lfloor x _1 *p \rfloor x1p;
再次切割若切割x2
x 3 x_3 x3= ⌊ x 1 ∗ p ⌋ \lfloor x _1 *p \rfloor x1p+ q q q;
x 4 x_4 x4= x 1 x_1 x1- ⌊ x 1 ∗ p ⌋ \lfloor x _1 *p \rfloor x1p+ q q q;
x 5 x_5 x5= ⌊ ( x 2 + q ) ∗ p ⌋ \lfloor (x _2+q) *p \rfloor (x2+q)p;
x 6 x_6 x6= x 2 x_2 x2- ⌊ ( x 2 + \lfloor (x _2+ (x2+q ) ∗ p ⌋ ) *p \rfloor )p;

x 3 x_3 x3= ⌊ x 1 ∗ p + q ⌋ \lfloor x _1 *p +q\rfloor x1p+q
证明:若 x 3 x_3 x3> x 5 x_5 x5:
x 1 ∗ p + q > x 2 ∗ p + q ∗ p x_1*p+q>x_2*p+q*p x1p+q>x2p+qp
p ∗ q < q p*q<q pq<q
所以成立。

x 4 > x 6 x_4>x_6 x4>x6的证明过程省略。

所以把最开始的长度从大到小排好序,每条蚯蚓切割之后的长度也是分别递减。
所以开三个队列,一个储存原长,一个存 ⌊ p x ⌋ \lfloor px\rfloor px另一个存 x − ⌊ p x ⌋ x-\lfloor px\rfloor xpx

代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n,m,q,u,v,t;
double p;
int a[100010];
queue<int >h,h1,h2;
int delta;
int get_max()
{
    int len = INT_MIN;
    int flag;
    if(!h.empty() && h.front() > len ) flag = 0,len = h.front();
    if(!h1.empty() && h1.front() > len ) flag = 1,len = h1.front();
    if(!h2.empty() && h2.front() > len ) flag = 2,len = h2.front();
    if(flag == 0) h.pop();
    if(flag == 1) h1.pop();
    if(flag == 2) h2.pop();
    return len;
}
int main()
{
    cin>>n>>m>>q>>u>>v>>t;
    p = (u * 1.0) / v;
    
    for(int i = 1;i <= n;i ++)
        scanf("%d",&a[i]);
    sort(a + 1,a + n + 1);
    for(int i = n;i >= 1;-- i)
        h.push(a[i]);
    for(int i = 1;i <= m;i ++)
    {
        LL len = get_max();
        len += delta;
        if( i % t == 0) printf("%lld ",len);
        int px = len * p;
        delta += q;
        int hh1 = px - delta;
        int hh2 = len - px - delta;
        h1.push(hh1);
        h2.push(hh2);
    }
    puts(" ");
    for(int i = 1;i <= n + m;i ++)
    {
        LL len = get_max() + delta;
        if(i % t == 0) printf("%lld ",len);
    }
    puts(" ");
}
直方图中最大矩形
单调栈问题

题目

解析:找一个栈存一个单调递增序列。
当遇到一个数比栈顶元素大,直接加入,否则弹出栈顶,直到栈顶小于当前元素为止。弹出的同时记录宽度,同时更新答案(或者从左边扫描一遍,再从右边扫描一遍,计算对于每个矩形而言左右两边能够扩展的最大长度,然后再计算)

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long LL;
typedef pair<LL,int > PII;

LL a[N];
int rt[N],lt[N];
int n;
int main()
{
    while(cin >> n && n)
    {
        stack<PII > r,l;
        r.push(make_pair(-1,0));
        l.push(make_pair(-1,n + 1));
        for(int i = 1;i <= n;i ++)
        {
            scanf("%lld",&a[i]);
            while(a[i] <= r.top().first)
                r.pop();
            rt[i] = r.top().second;
            r.push(make_pair(a[i],i));
        }
        
        for(int i = n;i >= 1;i --)
        {
            while(a[i] <= l.top().first)
                l.pop();
            lt[i] = l.top().second;
            l.push(make_pair(a[i],i));
        }
        long long ans=0;
        for(int i = 1;i <= n; i ++)
        {
            ans = max(ans,(LL)( lt[i] - rt[i] - 1 ) * a[i]);
        }
        cout << ans << endl;
    }
    return 0;
}
小组队列

题目
开一些队列来储存此时每个小组对应的人,再来一个储存目前还有哪些小组。

#include<bits/stdc++.h>
using namespace std;
const int N=1000009;
int belong[N];
int cnt,n;
queue<int >a[1010];
queue<int >group;
bool g[1010];
int main()
{
    while(cin>>n && n)
    {
        for(int i = 1;i <= n;i ++)
        while(!a[i].empty()) a[i].pop();
        while(!group.empty()) group.pop();
        cnt++;
        printf("Scenario #%d\n",cnt);
        for(int i = 1;i <= n;++ i)
        {
            int num,x;
            cin>>num;
            for(int j = 1;j <= num;++ j)
            scanf("%d",&x),belong[x] = i;
        }
        string s;
        int x;
        while(cin>>s && s!= "STOP")
        {
            if(s == "ENQUEUE")
            {
              scanf("%d",&x);
              int number = belong[x];
              if(a[number].empty()) group.push(number);
              a[number].push(x);
            }
            else
            {
                int number=group.front();
                printf("%d\n",a[number].front());
                a[number].pop();
                if(a[number].empty())
                group.pop();
            }
        }
        printf("\n");
    }
    return 0;
}
最大子序和

题目
单调队列。
找一个双端队列储存递增的前缀和(每一段子序和用前缀和相减)。
如果下一个元素小于当前队列队尾,就一直弹出队尾元素(因为对于之后的计算而言,在前面的元素如果大于后面的元素,它的竞争力就一定不如后面新加进来的元素),然后把这个元素加进去,如果队头元素的位置与现在所在位置的距离超过了m - 1.就弹出队头元素。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=300009;
int q[N];
LL sum[N];
int ans=INT_MIN;
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= n;i ++)
    {
        scanf("%lld",&sum[i]);
        sum[i] += sum[i - 1];
        
    }
    long long res = INT_MIN;
    int hh = 0,tt = 0;
    for(int i = 1;i <= n;i ++)
    {
        if(i - q[hh] > m) hh++;
        res = max(res,sum[i] - sum[q[hh]]);
        while(hh <= tt && sum[q[tt]] >= sum[i]) tt--;
        q[++ tt] = i;
    }
    cout<<res;
    return 0;
}
双端队列

题目
这道题倒推一下,先把所有数连同它们的序号一起按照元素大小递增排序。
然后由于利用双端队列,所以在每一个双端队列中,因为元素的序号,里面的元素的序号一定是一个单谷函数(先递减再递增)
因为要求最少的双端队列,所以我们就要要求双端队列个数最少。
然后处理方法看代码

#include<bits/stdc++.h>
using namespace std;
const int N=200010;
pair<int ,int > a[N];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++)
        scanf("%d",&a[i].first),a[i].second = i;//读入数据时pair储存大小和序号
    
    sort(a + 1,a + n + 1);//排序
    
    int last = INT_MAX,flag=-1;//last用来表示循环时上一个数的值,flag表示之前是递增的还是递减的
    int ans = 1;
    for(int i = 1;i <= n;)
    {
        int j = i;
        int v = a[i].first;//j表示从i到j的区间元素大小相同,v表示元素大小
        
        while(a[j + 1].first == v && j + 1 <= n) j ++;//计算j
        
        int p_max = a[j].second, p_min = a[i].second;//找到i到j中序号最大和最小的
        
        if(flag == -1)//如果上一次是递减
        {
            if(p_max < last) last = p_min;//如果i到j的最大值都小于last,那么整个这一段都可以放进这个递减序列
            else last = p_max, flag = 1;//否则就置为递增序列
        }//
        else
        {
            if(p_min > last) last = p_max;//如果最小值都比last大,那么整个这一段就都可以放进递增序列
            else last = p_min,ans ++,flag = -1;//否则重新开一段,变成递减序列
        }//这里也是差不多的
        i = j + 1;//更新i
    }
    cout<<ans;
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值