【暖*墟】 #洛谷提高网课# 8.1初级数据结构(1)

链表

 当平时使用数组时,有时会遇到一些问题:

  • 在删除一个数据时,为了保证数据的连续性,
  • 需要将删除点右边的数据、全部往左移一格。
  • 在插入一个数据时,为了保证数据的连续性,
  • 需要将插入点右边的数据、全部往右移一格。
  •  每次移动的时间复杂度为O(N),如果元素较多时会时间超限。

 链表一个位置包括了自己位置上的值下一个值所在的位置

这样链表就可以实现O(1)的插入/删除,同时保证数据的连续性。

一般使用数组模拟。v[i]表示当前位置的数字,nex[i]指向下个位置的数组坐标。

  • 在使用链表时可能需要知道上个位置在哪里,于是有了双向链表
  • 双向链表多使用一个pre[i]表示上个位置的数组坐标。

栈和队列

 栈在一端的出口同时控制加入和踢出,另一端的出口为封闭的, 满足后进先出原则。

 队列在尾控制加入,在头控制踢出,满足先进先出原则。

  • 栈:用一个数组S代表栈,l为元素数量,加入元素x。
  • S[++l]=x;弹出栈顶元素到x;x=S[l--];
  • 队列:用一个数组Q代表队列,l为队列头,r为队列尾,加入元素x。
  • Q[++r]=x;弹出队头元素到x;x=Q[l++]。

单调栈和单调队列

单调栈除了满足普通栈的性质外,还满足从栈顶到栈底的元素具有严格的单调性。

假设我们维护的是一个单调递减的栈: (找到合适的位置)

• 若进栈的元素为x,栈顶元素为S[l]。

• 那么当x>=S[l]时弹出栈顶元素,直到x<S[l]为止,压入元素x。

单调队列除了满足普通队列的性质外,还满足从队列头到队列尾的元素具有严格的单调性。

假设我们维护的是一个单调递减的队列:

• 若进队的元素为x,队尾元素为Q[r]。

• 那么当x>=Q[r]时弹出队尾元素,直到x<Q[r]为止,加入元素x。

例题1 - 洛谷P1886 滑动窗口 (单调队列)

例题2 - 洛谷P1725 琪露诺(单调队列优化dp)

  • 格子编号为0到N,每次移动可以到[i+L,i+R]中的一格,
  • 然后收集格子上的冰冻值A[i],最后一次移动时只要大于N 就算到达。
  • 求经过的格子最大冰冻值和。

SOLUTION

代码实现

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<cmath>
using namespace std;

int n,l,r,a[200005],f[400005];
int q[200005],head=1,tail=0;

int main(){
    cin>>n>>l>>r;
    for(int i=0;i<=n;++i) scanf("%d",&a[i]); 
    for(int i=n+l;i>=l;--i){
    //反向算,方便确定来源,dp填表法

        while(f[q[tail]]<=f[i]&&head<=tail) tail--;
        //↑↑↑单调队列找插入位置,栈底的元素大 
        q[++tail]=i; //找到位置,把i加进单调队列
 
        f[i-l]=max(f[i-l],f[q[head]]+a[i-l]); 
        //区间最优选择进入dp比较
        
        if(q[head]==i+r&&head<=tail) head++;
    }

    cout<<f[0]<<endl;
    return 0;
}

例题3 - 洛谷P1419 寻找段落(前缀和+二分答案)

  • 给定一个长度为n的序列a[i],定义a[i]为第i个元素的价值。
  • 现在需要找出序列中最有价值的“段落”。段落的定义是长度在[S,T]之间的连续序列。
  • 最有价值段落是指平均值最大的段落,段落的平均值=段落总价值/段落长度。

SOLUTION

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

const int maxn = 100000+10;

int A[maxn],n,s,t;
double sum[maxn];
int q[maxn],front,rear;

bool can(double x) {
    sum[0]=0;
    for(int i=1;i<=n;i++) //处理前缀和
        sum[i] = sum[i-1]+A[i]-x;
    front=1; rear=0;  //初始化单调队列 
    for (int i = 1; i <= n; i++) {  
        if (i >= s) {  //足够s个 
            while (rear >= front && sum[i - s] < sum[q[rear]])  
                rear--;  //↑↑↑ front为在i-t..i-s区间内的最小值
            q[++rear] = i - s;  //入队区间起点-1 
        }
        if (front <= rear && q[front] < i - t) 
            front++;  //维护区间i-t  
        if (front <= rear && sum[i] - sum[q[front]] >= 0) 
            return true; //有大于0的区间和说明最大平均值还可以更大   
    }
    return false;
}

int main() {
    scanf("%d%d%d",&n,&s,&t);
    double L=0,R=0;
    for(int i=1;i<=n;i++) {
        scanf("%d",&A[i]);
        R=max(R,(double)A[i]); //预处理找出max
    }
    while(R-L>1e-4) { //二分答案
        double M=L+(R-L)/2;
        if(can(M)) L=M;
        else R=M;
    }
    printf("%.3lf\n",L);                                            
    return 0;
}

例题4 - 洛谷P1823 音乐会的等待(单调栈维护)

  • N个人正在排队进入一个音乐会。人们想在队伍里寻找自己的熟人。
  • 队列中任意两个人A和B,如果他们相邻或他们之间没有人比A或B高,
  • 那么他们是可以互相看得见的。写一个程序计算出有多少对人可以互相看见。

SOLUTION

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
#define ll long long
using namespace std;
    
ll n,ans;
ll z[500010],now,tmp,i;

inline int read(){ //快读 
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int main(){
    scanf("%lld",&n);
    tmp=read(); z[1]=tmp; now=1;
    for(i=1;i<n;i++){
        tmp=read();
        if(tmp<z[now]) { ans++; z[++now]=tmp; }
        else{
            int l=1,r=now,m;
            while(l<r){
                m=(l+r)>>1; //取mid 
                if(r==l+1) m=r;
                if(z[m]>tmp) l=m; 
                else r=m-1;
            }
            ans+=now-l+1; //增加的人数 
            while(now>0&&z[now]<tmp) now--;
            z[++now]=tmp;
        }
    }
    printf("%lld",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/flora715/article/details/81329632