洛谷【数据结构1-1】线性表

P3156 【深基15.例1】询问学号

传送门

题目描述

有 n(n2×1^6) 名同学陆陆续续进入教室。我们知道每名同学的学号(在 1 到 10^9之间),按进教室的顺序给出。上课了,老师想知道第 i 个进入教室的同学的学号是什么(最先进入教室的同学 i=1),询问次数不超过 10^5次。

输入格式

第一行 2 个整数 n 和 m,表示学生个数和询问次数。

第二行 n 个整数,表示按顺序进入教室的学号。

第三行 m 个整数,表示询问第几个进入教室的同学。

输出格式

m 个整数表示答案,用换行隔开。

输入输出样例

输入 #1
10 3
1 9 2 60 8 17 11 4 5 14
1 5 9
输出 #1
1
8
5
解题思路:这,,直接用一个数组储存,然后输出就行
code:
#include<iostream>
using namespace std;
#define N 2000005
int a[N];
int n,m;
int main(void)
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;++i)
    cin>>a[i];
    while(m--)
        cin>>n,cout<<a[n]<<'\n';
    return 0;
}

P3613 【深基15.例2】寄包柜

传送门

题目描述

超市里有 n(n105) 个寄包柜。每个寄包柜格子数量不一,第 ii 个寄包柜有 ai(ai105) 个格子,不过我们并不知道各个 a_iai 的值。对于每个寄包柜,格子编号从 1 开始,一直到 ai。现在有 q(q1^5) 次操作:

  • 1 i j k:在第 i 个柜子的第 j个格子存入物品 k(0k1^9)。当 k=0 时说明清空该格子。
  • 2 i j:查询第 i 个柜子的第 j 个格子中的物品是什么,保证查询的柜子有存过东西。

已知超市里共计不会超过 10^7个寄包格子,ai 是确定然而未知的,但是保证一定不小于该柜子存物品请求的格子编号的最大值。当然也有可能某些寄包柜中一个格子都没有。

输入格式

第一行 2 个整数 n 和 q,寄包柜个数和询问次数。

接下来 q 个整数,表示一次操作。

输出格式

对于查询操作时,输出答案。

输入输出样例

输入 #1

5 4

1 3 10000 114514

1 1 1 1

2 3 10000

2 1 1

输出 #1

114514

1

解题思路:我们看到n的范围是1e5,ai的范围是1e5,所以直接开数组空间会炸掉

所以我们可以写个结构体,然后里面用vector表示寄包柜的格子,因为寄包格子不会超过1e7

所以可以开两个vector做,一个表示格子数,另一个存储物品,但是注意每次查询的时候从后向前搜索,找到该格子的位置就break。

否则可能会找到多个答案,但是之前的那些答案对我们并没有什么用,因为更新掉了。

code:

#include <iostream>
#include <vector>
#define N 100005
using namespace std;
struct node
{
    vector<int> num, key; //num存储格子,key存储格子里面的物品
    int s = 0;
} a[N];//a[i]的每个元素表示一个寄包柜
int n, q;
int main(void)
{
    ios::sync_with_stdio(false);
    cin >> n >> q;
    int t, i, j, k, p;
    while (q--)
    {
        cin >> p >> i >> j;
        if (p == 1)
        {
            cin >> k;
            a[i].s++;
            a[i].num.push_back(j);//更新num的值
            a[i].key.push_back(k);//更新key的值
        }
        else
        {
            for (int t = a[i].s; t >= 0; --t)
            {
                if (a[i].num[t] == j)//当找到j这个格子就跳出,因为前面的元素没有用了。
                {
                    cout << a[i].key[t] << '\n';
                    break;
                }
            }
        }
    }
    return 0;
}

 

P1449 后缀表达式

传送门

题目描述

所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。

如:3*(5–2)+7对应的后缀表达式为:3.5.2.-*7.+@。’@’为表达式的结束符号。‘.’为操作数的结束符号。

输入格式

输入:后缀表达式

输出格式

输出:表达式的值

输入输出样例

输入 #1

3.5.2.-*7.+@

输出 #1

16

说明/提示

字符串长度,1000内。

解题思路:看到后缀表达式,首先想到的就是栈,先写一个读数字的函数,遍历表达式

把每次读到的数字放进栈中,如果遇到运算符,那么栈中的元素个数必定大于等于2

所以直接取出栈顶元素,然后pop再和栈顶元素运算,再pop,再把新运算出来的结果放进栈顶

最后直接输出栈顶元素就行(必定只有一个元素,最后的元素可pop,也可不用pop)

code:

#include <cstring>
#include <iostream>
#include <stack>
using namespace std;
string str;
stack<int> p;
int t, times;
int get(int i)//获取字符串中数字的函数
{
    int j = i;
    int key = str[i] - '0';
    for (i++; i < str.length(); ++i)//模拟取值
    {
        if (str[i] >= '0' && str[i] <= '9')
        {
            key = key * 10 + str[i] - '0';
        }
        else
            break;
    }
    times = i - j;//主串中要跳过的字符数
    return key;
}
int main(void)
{

    ios::sync_with_stdio(false);
    int sum = 0;
    cin >> str;
    for (int i = 0; i < str.length(); ++i)
    {
        if (str[i] >= '0' && str[i] <= '9')
        {
            t = get(i);
            p.push(t);//把结果push进去
            i += times;//跳过无用字符
        }
        else if (str[i] == '+')
        {
            sum = p.top();
            p.pop();
            sum += p.top();
            p.pop();
            p.push(sum);
        }
        else if (str[i] == '-')
        {
            sum = p.top();
            p.pop();
            sum = p.top() - sum;
            p.pop();
            p.push(sum);
        }
        else if (str[i] == '*')
        {
            sum = p.top();
            p.pop();
            sum *= p.top();
            p.pop();
            p.push(sum);
        }
        else if (str[i] == '/')
        {
            sum = p.top();
            p.pop();
            sum = p.top() / sum;
            p.pop();
            p.push(sum);
        }
        else if (str[i] == '@')
            break;
    }
    cout << p.top() << endl;//栈中必定只有一个元素
    return 0;
}

P1996 约瑟夫问题

传送门

题目描述

n 个人围成一圈,从第一个人开始报数,数到 m 的人出列,再由下一个人重新从 11 开始报数,数到 m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

输入格式

输入两个整数 n,m

输出格式

输出一行 n 个整数,按顺序输出每个出圈人的编号。

输入输出样例

输入 #1

10 3

 输出 #1

3 6 9 2 7 1 8 5 10 4

说明/提示

1m,n100

解题思路:经典队列模拟问题,当然也可用数学方法做(更快),我们直接用队列模拟,如果队首是该出圈的人

就把他输出,然后pop掉(就出圈了,不用再数),否则先把队首元素先push进队尾,再pop掉(类似于重新排队),直到队空退出。

code:

#include <iostream>
#include <queue>
using namespace std;
int main()
{
    int n, m;
    queue<int> p;
    cin >> n >> m;
    for (int i = m; i <= n; ++i)//先把从[m,n]的元素加入队列,第一个肯定第一个输出,
                   //其实这题数据挺弱的,m其实可能大于n的,但是这个代码过了
                   //%%%%
p.push(i); for (int i = 1; i < m; ++i) p.push(i); int i = 0; while (p.size()) { if (i % m == 0)//当前这个人为出圈的人 cout << p.front() << " ";//输出 else p.push(p.front());//重新排队 p.pop(); i++; } }

 

P1160 队列安排

传送门

题目描述

一个学校里老师要将班上N个同学排成一列,同学被编号为1N,他采取如下的方法:

  1. 先将1号同学安排进队列,这时队列中只有他一个人;

  2. 2N号同学依次入列,编号为i的同学入列方式为:老师指定编号为i的同学站在编号为1(i1)中某位同学(即之前已经入列的同学)的左边或右边;

  3. 从队列中去掉M(M<N)个同学,其他同学位置顺序不变。

在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。

输入格式

1行为一个正整数N,表示了有N个同学。

2-N行,第ii行包含两个整数k,p,其中k为小于ii的正整数,p0或者1。若p0,则表示将ii号同学插入到k号同学的左边,p1则表示插入到右边。

N+1行为一个正整数M,表示去掉的同学数目。

接下来M行,每行一个正整数x,表示将x号同学从队列中移去,如果x号同学已经不在队列中则忽略这一条指令。

输出格式

1行,包含最多N个空格隔开的正整数,表示了队列从左到右所有同学的编号,行末换行且无空格。

输入输出样例

输入 #1

4

1 0

2 1

1 0

2

3

3

输出 #1

 

2 4 1

说明/提示

样例解释:

将同学2插入至同学1左边,此时队列为:

2 1

将同学33插入至同学2右边,此时队列为:

2 3 1

将同学4插入至同学1左边,此时队列为:

2 3 4 1

将同学3从队列中移出,此时队列为:

2 4 1

同学3已经不在队列中,忽略最后一条指令

最终队列:

2 4 1

数据范围

对于20%的数据,有N10;

对于40%的数据,有N1000;

对于100%的数据,有N,M100000。

解题思路:可能大多数人想到的就是直接写一个链表模拟,但是这样会T的(看数据就知道,直接用链表做的话可能只能的40%的分?)

我的思路是开三个数组,第一个存储同学是否存在,第二个存储当前同学左边的同学的位置,第三个存储当前同学右边的位置,然后根据指令不断更新后两个数组(类似于链表的插入更新)。

最后单独写一个输出的函数,我们不难发现第0个元素一定是最前面的,(有点像有头节点的的链表,而第0个元素就是这个头节点,那么就好办了)

code:

#include <iostream>
using namespace std;
#define N 100005
int al[N], ar[N], a[N];
int n, m, k, p, x;
void print()
{
    int t = ar[0];
    while (ar[t])
    {
        cout << t << " ";
        t = ar[t];
    }
    cout << t << '\n';//注意这里的格式,好像是要行末输出换行且无空格
}
int main(void)
{
    ios::sync_with_stdio(false);
    cin >> n;
    al[1] = 0;//初始化
    ar[n] = 0;//初始化,在判断边界的时候有用
    ar[0] = 1;//初始化
    for (int i = 2; i <= n; ++i)
    {
        cin >> k >> p;
        if (p) //将i号同学插入到k的右边
        {
            if (ar[k])//如果右边有元素
            {
                al[ar[k]] = i;
                ar[i] = ar[k];
                al[i] = k;
                ar[k] = i;
            }
            else//如果插入的是最后一个位置(也就是第k个同学的右边什么都没有=_=)
            {
                ar[k] = i;
                al[i] = k;
            }
        }
        else //将i号同学插入到k的左边
        {
            //由于插在左边的时候,不可能插在头节点的左边,所以我们可以直接这样做>_<
            //这样也能更新头节点右边的值
            ar[al[k]] = i;
            al[i] = al[k];
            ar[i] = k;
            al[k] = i;
        }
        //        cout<<"--------------------\n";    这是我调试用的>_<,下面那个也是
        //        print();
        //        cout<<"--------------------\n";
    }
    cin >> m;
    while (m--)
    {
        cin >> x;
        if (a[x])//如果当前同学已经被删除了
            continue;
        else//删除操作
        {
            if (al[x] && !ar[x])//还是考虑最右边的情况
                ar[al[x]] = 0;
            else
                ar[al[x]] = ar[x], al[ar[x]] = al[x];
            a[x] = 1;
        }
        //        cout<<"--------------------\n";
        //        print();
        //        cout<<"--------------------\n";
    }
    print();//输出函数
    return 0;
}

 

P1540 机器翻译

传送门

题目背景

小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。

题目描述

这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词的中文含义,如果内存中有,软件就会用它进行翻译;如果内存中没有,软件就会在外存中的词典内查找,查出单词的中文含义然后翻译,并将这个单词和译义放入内存,以备后续的查找和翻译。

假设内存中有 M 个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过 M1,软件会将新单词存入一个未使用的内存单元;若内存中已存入 M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。

假设一篇英语文章的长度为 N 个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。

输入格式

共 22 行。每行中两个数之间用一个空格隔开。

第一行为两个正整数 M,N,代表内存容量和文章的长度。

第二行为 NN 个非负整数,按照文章的顺序,每个数(大小不超过 1000)代表一个英文单词。文章中两个单词是同一个单词,当且仅当它们对应的非负整数相同。

输出格式

一个整数,为软件需要查词典的次数。

输入输出样例

输入 #1

3 7

1 2 1 5 4 4 1

输出 #1

5

数据范围

对于 10% 的数据有 M=1,N5;

对于 100% 的数据有 1M100,1N1000。

解题思路:我们可以开一个队列还有一个数组,队列保存当前内存栈中存储的单词,数组表示该单词是否存在内存栈中

然后我们模拟这个过程,一开始内存栈中的元素为0,所有的单词都是需要查询的,不断的读入单词,然后查看数组是否存在

当然,当读入的元素个数大于k的时候就要更新内存栈和p数组。

code:

#include <iostream>
#include <map>
#include <queue>
using namespace std;
int p[1005];
int main(void)
{
    ios::sync_with_stdio(false);
    int m, n, t, k = 0;
    queue<int> q;
    cin >> m >> n;
    int sum = 0;//sum表示的是查询的次数
    for (int i = 1; i <= n; ++i)
    {
        cin >> t;
        if (k < m)//当内存栈中存储的数据小于k的时候
        {
            if (p[t] == 0)//如果该单词未被存储
            {
                sum++, p[t] = 1, k++;
                q.push(t);
            }
        }
        else
        {
            if (p[t] == 0)
            {
                sum++, p[t] = 1;
                p[q.front()] = 0;//更新p数组
                q.pop();//更新内存栈
                q.push(t);
            }
        }
    }
    cout << sum << endl;
    return 0;
}

 

P2058 海港

传送门

题目描述

小K是一个海港的海关工作人员,每天都有许多船只到达海港,船上通常有很多来自不同国家的乘客。

小K对这些到达海港的船只非常感兴趣,他按照时间记录下了到达海港的每一艘船只情况;对于第i艘到达的船,他记录了这艘船到达的时间ti (单位:秒),船上的乘 客数ki,以及每名乘客的国籍 x_{i,1}, x_{i,2},…,x_{i,k}xi,1,xi,2,,xi,k

小K统计了n艘船的信息,希望你帮忙计算出以每一艘船到达时间为止的2424小时(2424小时=86400秒)内所有乘船到达的乘客来自多少个不同的国家。

形式化地讲,你需要计算n条信息。对于输出的第i条信息,你需要统计满足ti86400<tpti的船只p,在所有的xp,j中,总共有多少个不同的数。

输入格式

第一行输入一个正整数n,表示小K统计了n艘船的信息。

接下来n行,每行描述一艘船的信息:前两个整数tiki分别表示这艘船到达海港的时间和船上的乘客数量,接下来ki个整数x_{i,j}xi,j表示船上乘客的国籍。

保证输入的ti是递增的,单位是秒;表示从小K第一次上班开始计时,这艘船在第t_iti秒到达海港。

保证 1n105,ki3105 ,1x(i,j)105, 1t(i1)ti10^9。

其中ki表示所有的ki的和。

输出格式

输出n行,第i行输出一个整数表示第i艘船到达后的统计信息。

输入输出样例

输入 #1

3

1 4 4 1 2 2

2 2 2 3

10 1 3

输出 #1

3

4

4

输入 #2

4

1 4 1 2 2 3

3 2 2 3

86401 2 3 4

86402 1 5

输出 #2

3

3

3

4

解题思路:由于ti是递增的,我们可以考虑模拟时间流的方式进行运算,设置三个数组

第一个表示第i个乘客的国籍,第二个表示第第i个国籍的人数,第三表示第i个乘客的到达时间

定义一个sum,表示的是当前国家数目,每次输入x(i,j)的时候,如果x国籍的人数未0,sum就++

然后每次更新的时候把时间差大于24小时的人数略过,然后更新sum的值(如果该国籍数为0,sum--)

具体看代码吧>_<

code:

#include <iostream>
using namespace std;
#define N 100005
int t[N * 3], X[N], KI[N * 3]; //t数组存放的是第i个乘客的国籍
                               //X数组存放的是第i个国籍的人数
                               //KI数组存放的是第i个乘客的到达时间
int sum, n;//sum表示的是当前乘客的国籍总数
int ti, ki, temp, cnt, ct;
int main(void)
{
    cnt = ct = sum = 0;
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 0; i < n; ++i)
    {
        cin >> ti >> ki;
        for (int j = 1; j <= ki; ++j)
        {
            cin >> temp;
            t[cnt] = temp;
            KI[cnt++] = ti;
            if (!X[temp])//如果temp的国籍人数为0,sum自增
                sum++;
            X[temp]++;
        }
        while (ti - KI[ct] >= 86400)//要用while循环,因为时间差可能好几个都大于86400
        {
            X[t[ct]]--;
            if (!X[t[ct]])//如果该乘客的国籍人数为0,国籍总数就--
                sum--;
            ct++;
        }
        cout << sum << '\n';//直接输出就行
    }
    return 0;
}
//关键就是维护三个数组,原理很简单的

 

P1241 括号序列

传送门

题目描述

定义如下规则序列(字符串):

1.空序列是规则序列;

2.如果S是规则序列,那么(S)和[S]也是规则序列;

3.如果A和B都是规则序列,那么AB也是规则序列。

例如,下面的字符串都是规则序列:

(),[],(()),([]),()[],()[()]

而以下几个则不是:

(,[,],)(,()),([()

现在,给你一些由‘(’,‘)’,‘[’,‘]’构成的序列,你要做的,是补全该括号序列,即扫描一遍原序列,对每一个右括号,找到在它左边最靠近它的左括号匹配,如果没有就放弃。在以这种方式把原序列匹配完成后,把剩下的未匹配的括号补全。

输入格式

输入文件仅一行,全部由‘(’,‘)’,‘[’,‘]’组成,没有其他字符,长度不超过100。

输出格式

输出文件也仅有一行,全部由‘(’,‘)’,‘[’,‘]’组成,没有其他字符,把你补全后的规则序列输出即可。

输入输出样例

输入 #1
([()

输出 #1

()[]()

说明/提示

将前两个左括号补全即可。

 解题思路:这题真“阅读理解题”,题目的含义我开始看了好几遍都没看懂,好了看重点:对每一个右括号找到最近的左括号

如果不匹配(比如:右括号‘)’,但是找到的左括号是'[')或者没找到就放弃,也就是说,题目的意思是首先满足找到一个左括号

再判断是否匹配,如果匹配的话,在当前位置做个标记,有标记的直接输出,没有标记的就输出一个完整的左右括号。由于题目的数据很小,所以可以直接暴力。

code1:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <stack>
int is[105];
char ch[105];
using namespace std;
int main(void)
{
    ios::sync_with_stdio(false);
    cin >> ch;
    for (int i = 0; i <= strlen(ch); ++i)
    {
        if (ch[i] == ']')
        {
            for (int j = i - 1; j >= 0; --j)
            {
                if (!is[j] && ch[j] == '[')//如果匹配
                {
                    is[i] = is[j] = 1;//标记
                    break;//注意要跳出
                }
                else if (!is[j] && ch[j] == '(')//不匹配
                {
                    break;//直接跳出,不用看前面的!!!,这点太坑了T^T
                }
            }
        }
        else if (ch[i] == ')')//同上
        {
            for (int j = i - 1; j >= 0; --j)
            {
                if (!is[j] && ch[j] == '(')
                {
                    is[i] = is[j] = 1;
                    break;
                }
                else if (!is[j] && ch[j] == '[')
                {
                    break;
                }
            }
        }
    }
    for (int i = 0, len = strlen(ch); i < len; ++i)
    {
        if (is[i])//如果标记了,那么肯定能组合成一个完整的左右括号
            putchar(ch[i]);
        else
        {
            if (ch[i] == '(' || ch[i] == ')')//输出完整的左右括号
                printf("()");
            else if (ch[i] == '[' || ch[i] == ']')
                printf("[]");
        }
    }
    return 0;
}

当然上面的是O(n^2)的时间复杂度,我们可以想想优化,我们要搜索左边第一个左括号,其实不用遍历去搜索

我们把每次读到的左括号放进一个栈里面,每次匹配的时候拿出来比较就行。复杂度O(n)

code:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <stack>
int is[105];
char ch[105];
using namespace std;
int main(void)
{
    ios::sync_with_stdio(false);
    stack<char> last;
    stack<int> loc;
    cin >> ch;
    int len = strlen(ch);
    for (int i = 0; i < len; ++i)
    {
        if (ch[i] == '(' || ch[i] == '[')
            last.push(ch[i]), loc.push(i);
        else
        {
            if (ch[i] == ')')
            {
                if (last.size())
                {
                    if (last.top() == '(')
                    {
                        is[i] = is[loc.top()] = 1;
                        loc.pop();
                        last.pop();
                    }
                }
            }
            else if (ch[i] == ']')
            {
                if (loc.size())
                {
                    if (last.top() == '[')
                    {
                        is[i] = is[loc.top()] = 1;
                        loc.pop();
                        last.pop();
                    }
                }
            }
        }
    }
    for (int i = 0; i < len; ++i)
    {
        if (is[i])
            cout << ch[i];
        else
        {
            if (ch[i] == '(' || ch[i] == ')')
                cout << "()";
            else
                cout << "[]";
        }
    }
    return 0;
}

P4387 【深基15.习9】验证栈序列

传送门

题目描述

给出两个序列 pushed 和 poped 两个序列,其取值从 1 到 n(n100000)。已知入栈序列是 pushed,如果出栈序列有可能是 poped,则输出 Yes,否则输出 No。为了防止骗分,每个测试点有多组数据。

输入格式

第一行一个整数 q,询问次数。

接下来 q 个询问,对于每个询问:

第一行一个整数 n 表示序列长度;

第二行 n 个整数表示入栈序列;

第二行 n 个整数表示出栈序列;

输出格式

对于每个询问输出答案。

输入输出样例

输入 #1
2
5
1 2 3 4 5
5 4 3 2 1
4
1 2 3 4
2 4 1 3
输出 #1
Yes
No
解题思路:我们把输入的入栈序列不断放进一个栈里面,如果栈顶的元素和出栈的元素的值相同就把栈顶出栈,然后最后判断栈是否为空
如果为空,那么就是不可能,否则可能。
#include <bits/stdc++.h>
using namespace std;
#define N 100005
int q, n;
int a[N], b[N];
int main(void)
{
    ios::sync_with_stdio(false);
    cin >> q;
    while (q--)
    {
        stack<int> p;
        cin >> n;
        for (int i = 1; i <= n; ++i)
            cin >> a[i];
        for (int i = 1; i <= n; ++i)
            cin >> b[i];
        int i, j;
        for (i = 1, j = 1; i <= n; ++i)
        {
            p.push(a[i]);
            while (!p.empty() && p.top() == b[j])//判断是否能出栈,最关键的就是这个。。。
            {
                p.pop();
                j++;
            }
        }
        if (p.empty())
            puts("Yes");
        else
            puts("No");
    }
}

over

posted @ 2020-08-14 10:59  MangataTS  阅读(559)  评论(0编辑  收藏  举报