基础知识复习笔记 Day 1

时间:2022-11-16 22:17:03

基础知识复习笔记 Day 1

  • 链表
  • 队列与优先队列
  • 单调栈与单调队列


1、链表

链表是线性表的一种表示方法,链表不要求逻辑上相邻的元素物理位置上也相邻,因此没有顺序表在进行插入删除的缺点,但同时失去了顺序表可以随机存取的优点。
链表的特点是用一组任意的存储单元存储线性表的数据元素,这一组存储单元不要求是连续的。因此为了表示每个元素ai与其后继数据元素ai+1之间的逻辑关系,对于数据元素ai来说,除了存储其本身的信息之外,还需要存储一个指示其后继存储位置的信息。这两部分信息组成数据元素ai的存储映像,称为结点。

2、栈

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
栈中允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为后进先出表。

例题

P1981 表达式求值

题目描述

给定一个只包含加法和乘法的算术表达式,请你编程计算表达式的值。

输入输出格式

输入格式:
输入文件为 expr.in。

输入仅有一行,为需要你计算的表达式,表达式中只包含数字、加法运算符“+”和乘法运算符“*”,且没有括号,所有参与运算的数字均为 0 到 2^31-1 之间的整数。
输入数据保证这一行只有 0~ 9、+、*这 12 种字符。

输出格式:
输出文件名为 expr.out。

输出只有一行,包含一个整数,表示这个表达式的值。注意:当答案长度多于 4 位时,请只输出最后 4 位,前导 0 不输出。

输入输出样例

输入样例#1:
1+1*3+4

输出样例#1:
8

输入样例#2:
1+1234567890*1

输出样例#2:
7891

输入样例#3:
1+1000000003*1

输出样例#3:
4

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<queue>
using namespace std;
int n,top,yz;
char last;
int ans;
int sta[1000005];
const int mod=10000;
string c;
int main(){
    cin>>c;
    int l=c.size();
    c=c+'+';
    for(int i=0;i<l+1;i++){
        if(c[i]!='+'&&c[i]!='*'){
            yz*=10;
            yz%=mod;
            yz+=c[i]-'0';
        }
        else{
            sta[++top]=yz;
            yz=0;
            if(last=='*'&&top!=1){
                sta[top-1]*=sta[top];
                sta[top-1]%=mod;
                top--;
            }
            last=c[i];
        }
    }
    for(int i=1;i<=top;i++){
        ans+=sta[i];
        ans%=mod;
    }

    printf("%d",ans);

    return 0;
}

3、队列&优先队列

队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端进行删除操作,而在表的后端进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。

优先队列

说到优先队列其实就跟队列没有什么关系了。。。
优先队列实际上是一个堆
priority_queue <int> p;   ->大根堆
priority_queue <int,vector<int>,greater<int> > p;  -> 小根堆
q.top()查询堆顶
q.pop()删除  q.push()添加
如果是结构体的话重载一下大于小于运算符就行了

例题

洛谷p1090 合并果子

—— [ 传送门 ]

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
int n,yz,ans;
priority_queue <int,vector<int>,greater<int> > q;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&yz);
        q.push(yz);
    }
    while(!q.empty()){
        int x1=q.top();q.pop();
        if(q.empty()){
            printf("%d",ans);
            return 0;
        }
        int x2=q.top();q.pop();
        q.push(x1+x2);
        ans+=x1+x2;
    }

    return 0;
}

4、单调栈与单调队列

用单调栈可以迅速求出所有数向左或向右第一个比他大或比他小的数的位置,时间复杂度O(n)。
用单调队列能在O(n)的时间复杂度下维护许多个连续区间内的最大最小值。

例题1

很经典的题:滑动窗口 (洛谷P1886)

题目描述

现在有一堆数字共N个数字(N<=10^6),以及一个大小为k的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如:

The array is [1 3 -1 -3 5 3 6 7], and k = 3.

输入输出格式

输入格式:
输入一共有两行,第一行为n,k。

第二行为n个数。

输出格式:
输出共两行,第一行为每次窗口滑动的最小值

第二行为每次窗口滑动的最大值

输入输出样例

输入样例#1:
8 3
1 3 -1 -3 5 3 6 7
输出样例#1:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
说明

50%的数据,n<=10^5

100%的数据,n<=10^6

题解1

这道题有坑!!!!
有一个数据 m=0 真的很难受,需要特判。
然后这道题就是一道单调队列的裸题了,这个可以当板子咯。。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
int f_minn,b_minn,f_maxx,b_maxx;
struct que{
    int num,id;
    bool operator < (const que q) const{
        return num<q.num;
    }
    bool operator > (const que q) const{
        return num>q.num;
    }
    que & operator = (const que q) {
        num=q.num;
        id=q.id;
        return *this;
    }
}a[1000005],minn[1000005],maxx[1000005];
int ans_minn[1000005],ans_maxx[1000005];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i].num);
        a[i].id=i;
    }
    if(m==1){
        for(int i=1;i<=n;i++){
            printf("%d ",a[i].num);
        }printf("\n");
        for(int i=1;i<=n;i++){
            printf("%d ",a[i].num);
        }
        return 0;
    }
    b_minn=0;b_maxx=0;
    for(int i=1;i<=m-1;i++){
        while(minn[f_minn]>a[i]&&f_minn>=b_minn) f_minn--;
        minn[++f_minn]=a[i];
        while(maxx[f_maxx]<a[i]&&f_maxx>=b_maxx) f_maxx--;
        maxx[++f_maxx]=a[i];
    }
    for(int i=m;i<=n;i++){
        while(minn[b_minn].id<=i-m) b_minn++;
        while(minn[f_minn]>a[i]&&f_minn>=b_minn) f_minn--;
        minn[++f_minn]=a[i];
        ans_minn[++ans]=minn[b_minn].num;

        while(maxx[b_maxx].id<=i-m) b_maxx++;
        while(maxx[f_maxx]<a[i]&&f_maxx>=b_maxx) f_maxx--;
        maxx[++f_maxx]=a[i];
        ans_maxx[ans]=maxx[b_maxx].num;
    }
    for(int i=1;i<=ans;i++){
        printf("%d ",ans_minn[i]);
    }printf("\n");
    for(int i=1;i<=ans;i++){
        printf("%d ",ans_maxx[i]);
    }
    return 0;
}

例题2

Feel Good Poj2796(中文题在洛谷P2422良好的感觉) 以下是洛谷的题目:

题目描述

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入输出格式

输入格式:
输入文件名为 truck.in。

输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道

路。 接下来 m 行每行 3 个整数 x、 y、 z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意: x 不等于 y,两座城市之间可能有多条道路 。

接下来一行有一个整数 q,表示有 q 辆货车需要运货。

接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意: x 不等于 y 。

输出格式:
输出文件名为 truck.out。

输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货

车不能到达目的地,输出-1。

输入输出样例

输入样例#1:
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
输出样例#1:
3
-1
3
说明

对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q< 1,000;

对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q< 1,000;

对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,000。

题解2

ldx大佬的题解写的很详细,这里借用一下:

首先考虑,既然所求值为区间元素和与区间最小值的乘积
那么对于每一个数,我们找到一段最长的区间,使这个数是区间中的最小值
像这样对于每个数都找到一段符合条件的最长区间
然后在其中找到使一个答案最大的情况就完了
现在问题转化成了,如何找到对于每个数的最长区间,即找到每个数向左或向右第一个比他小的数的位置
直接暴搜显然是不行的,所以我们想到了用单调栈优化
我们维护一个递增的单调栈
当元素x被弹掉时,弹掉它的元素y就是从它向右数第一个比它小的元素
我们知道弹掉x的y一定在x的右面且比x小
如果在x与y之间有另一个比x小的数的话
x就应该在之前就被那个数弹掉了
所以y一定是x向右数第一个比它小的数

元素x在栈中的上一位y即是从它向左数第一个比它小的数
与之前的证明同理
若x和y之间有另一个比x小的数r
如果r比y大则r一定在栈中y的后面,那么这时候x的上一位就会是r而不是y
如果r比y小则r一定在之前就已经把y弹出了
两种情况都不可能,所以y一定是x向左数第一个比它小的数

不过当然了,做两次单调栈也是完全可以的

代码如下(这个是洛谷的)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
long long n,yz,ans,top,ansf,ansb;
struct stac{
    long long num,id;
    bool operator >= (const stac x) const {return num>=x.num;}
    bool operator == (const stac x) const {return num==x.num;}
    stac & operator = (stac & x) {num=x.num;id=x.id;return *this;}
}sta[100005],ne;
long long s[100005];
int main(){
    while(scanf("%lld",&n)==1){
        memset(s,0,sizeof(s));
        top=0;ans=0;
        for(long long i=1;i<=n;i++){
            scanf("%d",&ne.num); ne.id=i;
            s[i]=s[i-1]+ne.num;
            while(top!=0&&sta[top]>=ne){
                ans=max(ans,(s[i-1]-s[sta[top-1].id])*sta[top].num);
                top--;
            }
            sta[++top]=ne;
        }

        while(top){
            if(ans<(s[n]-s[sta[top-1].id])*sta[top].num)
                ans=max(ans,(s[n]-s[sta[top-1].id])*sta[top].num);
            top--;
        }

        printf("%lld",ans);
    }

    return 0;
}