个人贴,本周acm培训题解和踩坑记录

第一题:

题目描述

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

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

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

输入格式

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

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

输出格式

对于查询操作时,输出答案,以换行隔开。

输入输出样例

输入 #1

5 4
1 3 10000 118014
1 1 1 1
2 3 10000
2 1 1

输出 #1

118014
1

说明/提示

upd 2022.7.26:新增加一组 Hack 数据。

        这题打眼一看感觉挺简单的,但是随便一写提交就错,后来才明白这题的意思。

        这题大概表达的意思就是一个二维数组的存取问题,但是二维数组中很多元素都是空的(就是只有很少部分元素在程序运行时有存取),最简单粗暴的办法就是使用vector<int>嵌套vector<int>,但是这个二维数组的每一维最大上限是1E5啊,也就是说总量上限会达到恐怖的1E10,这已经是GB来衡量的数量级了,不用说,内存超限!

        但是仔细一看,题目总数的上限就是1E7远小于1E10,就是说我们事实上浪费了很多的存储空间。想到这里我想起了数据结构课上讲的稀疏矩阵的存储,但是稀疏矩阵的读取和写入,至少按照所学的方法,都需要循环,害怕会超时。无奈之下只好翻翻CSDN,看到了有种办法是可以用vector来嵌套map,就好奇的了解了map,没想到居然这么强大!使用起来也是十分方便,可以自己建立映射,访问时直接数组下标访问,无需担心越界!而且如果不按照顺序来写入,他还能自动给你排好序,最最重要的就是,不会浪费存储空间!!!简直就是外挂一般的存在。

        有了这么强大的外挂,自然不用担心过不去了,至于说代码,那不是轻轻松松简简单单吗?三把避过!

#include <vector>
#include <map>
#include <iostream>
using namespace std;
//如果使用vector嵌套vector会内存超界的
//再次感到C++标准库的强大!
int main()
{
    int n, q;
    cin >> n >> q;
    vector<map<int,int>> arr(n+1);  //vector下标越界的话会报错
    for(int i = 0; i < q; i++)
    {
        int choice;
        cin >> choice;
        if(choice == 1)
        {
            int x, y, m;
            cin >> x >> y >> m;
            arr[x][y] = m;
        }
        else
        {
            int x, y;
            cin >> x >> y;
            cout << arr[x][y] << endl;
        }
    }
    return 0;
}

第二题

题目描述

定义如下规则:

  1. 空串是「平衡括号序列」
  2. 若字符串 S 是「平衡括号序列」,那么 (S)和 [S] 也都是「平衡括号序列」
  3. 若字符串 A 和 B 都是「平衡括号序列」,那么 AB(两字符串拼接起来)也是「平衡括号序列」。

例如,下面的字符串都是平衡括号序列:

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

而以下几个则不是:

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

现在,给定一个仅由 ()[]构成的字符串 s,请你按照如下的方式给字符串中每个字符配对:

  1. 从左到右扫描整个字符串。
  2. 对于当前的字符,如果它是一个右括号,考察它与它左侧离它最近未匹配的的左括号。如果该括号与之对应(即小括号匹配小括号,中括号匹配中括号),则将二者配对。如果左侧未匹配的左括号不存在或与之不对应,则其配对失败。

配对结束后,对于 s 中全部未配对的括号,请你在其旁边添加一个字符,使得该括号和新加的括号匹配。

输入格式

输入只有一行一个字符串,表示 s。

输出格式

输出一行一个字符串表示你的答案。

输入输出样例

输入 #1

([()

输出 #1

()[]()

输入 #2

([)

输出 #2

()[]()

说明/提示

数据规模与约定

对于全部的测试点,保证 s 的长度不超过 100,且只含 ()[] 四个字符。

        显然,括号问题,肯定是要使用栈(stack)的,只不过是多了括号的输出罢了,写代码ing。

        但是题目似乎和我想象的不太一样,括号匹配问题是简单的,现在主要的问题就是是补括号,右括号失配好办,只要在前面补上相应的左括号就好,但是左括号失配的话就比较难办,所以考虑到这个问题,把括号的压栈类型设置为结构体,同时含有括号的类型和下标,这样回去匹配时就方便得多了,匹配后的字符串将写入原字符串。但是这样子就又犯了刻舟求剑的错误,因为insert后字符串后面元素的下标在改变,而记录的是原来的下标,这样容易出现混乱。所以在这种情况下避免使用insert。还在debug

--------------------------------------------------------------------------------------------------------------------------------

#include <iostream>
#include <vector>
#include <stack>
#include <deque>
using namespace std;
//定义一个输出匹配的括号的函数
char match(char c)
{
    switch(c)
    {
        case '(' :
            return ')';
        case ')' :
            return '(';
        case '[' :
            return ']';
        case ']' :
            return '[';
        default:
            return 0;
    }
}
//要记录下字符的类型和下标
struct cchar
{
    char ch;
    int pos;
    //加个构造函数这样的话会方便很多的
    cchar(char c = '\0', int p = 0):
        ch(c), pos(p) {}
};
//用堆栈的方法可以快速的判断是否平衡但是却难以记录是哪些导致不平衡的
//避免使用insert的方法就是在字符串中做标记,用另一个数组
int main()
{
    string str;
    cin >> str;
    vector<bool> arr(str.length(), 0); //用来记录哪个失配
    stack<cchar> zhan;
    //第一次我只管他哪些是失配的
    for(int i = 0; i < str.length(); i++)
    {
        if(str[i] == '(' || str[i] == '[')
        {
            zhan.push(cchar(str[i], i));
        }
        else if(str[i] == ')' || str[i] == ']')
        {
            if(zhan.empty()) //栈为空,只有右括号失配
            {
                arr[i] = 1;
            }
            else if(zhan.top().ch != match(str[i])) //这种情况是两个都失配了
            {
                arr[i] = 1;
                // arr[zhan.top().pos] = 1;
                // zhan.pop();
            }
            else
            {
                zhan.pop();                         //正常匹配时栈要弹出
            }
        }
    }
    while(!zhan.empty())
    {
        arr[zhan.top().pos] = 1;
        zhan.pop();
    }
    for(int i = 0; i < str.length(); i++)
    {
        if(str[i] == '(' || str[i] == '[')
        {
            putchar(str[i]);
            if(arr[i])
            {
                putchar(match(str[i]));
            }
        }
        else if(str[i] == ')' || str[i] == ']')
        {
            if(arr[i])
            {
                putchar(match(str[i]));
            }
            putchar(str[i]);
        }
    }
    putchar('\n');
    return 0;
}

        为了防止insert后下标出现混乱,我就采用计分的方法来“划掉”失配的括号,在我没有注释那两句前是不能过的,但是注释之后却意外地通过了。真奇怪啊,看了某大佬的博客我才知道,原来是前面没有匹配的括号留着后面可能还会匹配,看看这组数据可能会清楚很多:

输入:( [ ) ] )

输出:( [ ( ) ] )

        比如第一个中括号,和后面的小括号匹配失败,但是可以和最后面的中括号匹配成功,算到最后,真正匹配失败的括号才会被我们(强制匹配),所以,我们当匹配失败时,不能一棍子打死,继续留在栈里面,说不定他还能再跟别的括号匹配呢

        数组模拟线性表_Funing7的博客-CSDN博客这是大佬的博客

第三题

题目描述

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

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

输入格式

输入一行一个字符串 s,表示后缀表达式。

输出格式

输出一个整数,表示表达式的值。

输入输出样例

输入 #1

3.5.2.-*7.+@

输出 #1

16

说明/提示

数据保证,1≤∣s∣≤50,答案和计算过程中的每一个值的绝对值不超过 10^9。

        最开始死活看不懂,简单看了题解明白第三题的意思,我觉得读懂题了,这是就是so-easy;大体思路就是要有一个栈来存放操作数,因为没有优先级的问题,所以不需要使用栈来存放运算符,当读到运算符时,在栈里面取出两个操作数,再把结果写回栈,循环往复直到读完整个字符串就Ok了,这题也是本周唯一一个一次性通过的题。

#include <iostream>
#include <vector>
#include <stack>
using namespace std;
int caculate(int a, char sign, int b)
{
    switch(sign)
    {
        case '+':
            return a+b;
        case '-':
            return a-b;
        case '*':
            return a*b;
        case '/':
            return a/b;
    }
}
int main()
{
    stack<int> nums;
    char c;
    int now = 0;         //当前运算结果
    while((c = getchar()) != '@')
    {
        if(isdigit(c))
        {
            now = now*10 + (c-'0');
        }
        else if(c =='.')
        {
            nums.push(now);
            now = 0;
        }
        else
        {
            int a = 0, b = 0;
            b = nums.top();
            nums.pop();
            a = nums.top();
            nums.pop();
            nums.push(caculate(a, c, b));
        }
    }
    cout << nums.top();
    return 0;
}

第四题

题目描述

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

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

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

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

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

输入格式

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

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

第 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

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

2 3 1

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

2 3 4 1

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

2 4 1

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

最终队列:

2 4 1

数据范围

对于 20% 的数据,有 1≤N≤10;

对于 40% 的数据,有 1≤N≤1000;

对于 100% 的数据,有 1≤N,M≤100000

        看起来挺简单是不是,但是这题可不是一盏省油的灯,用deque,超时,list,超时,使用std的find函数,还是超时,无语了都。。。

        但是我现在想起来第四题好像可以用我们老师课上讲的静态链表来做,就是用一个数组来做一个伪链表,需要双向链表。这样子很方便来查找学生的信息而不是需要循环遍历。应该可以解决超时的问题。接下来就尝试了静态双向链表,确实能解决这个问题(只用单向链表插入会更方便,但是不利于问题的解决),只是因为不够熟练,格某在双向链表的插入删除操作时犯了一会难。在熟悉双向链表的插入与删除操作后,问题就变得简单了,通过!上代码!

#include <iostream>
#include <vector>
using namespace std;
struct mynode   //因为数组下标就是data
{
    mynode(int a = 0, int b = 0):
        left(a), right(b) {}

    int left;   //左边的人编号,没有就是0
    int right;  //右边的人编号,没有就是0
};

int main()
{
    int N;
    cin >> N;
    vector<mynode> dui(N+1);
    for(int i = 2; i <= N; i++)
    {
        int a, m;
        cin >> a >> m;
        if(m == 0)
        {
            dui[i].left = dui[a].left;
            dui[i].right = a;
            if(dui[i].left != 0)
                dui[dui[i].left].right = i;
            dui[dui[i].right].left = i;
        }
        else
        {
            dui[i].left = a;
            dui[i].right = dui[a].right;
            dui[dui[i].left].right = i;
            if(dui[i].right != 0)
                dui[dui[i].right].left = i;
        }
    }
    int M;
    cin >> M;
    for(int i = 0; i < M; i++)
    {
        int a;
        cin >> a;
        //在出队这个地方更是要慎重!!
        dui[dui[a].left].right = dui[a].right;
        dui[dui[a].right].left = dui[a].left;
        dui[a].left = 0;
        dui[a].right = 0;
    }
    int first = 0;                  //找到第一个人
    for(first = 1; first <= N; first++)
    {
        if(dui[first].left == 0 && dui[first].right != 0)
            break;
    }
    for(int i = first; i != 0; i = dui[i].right)
    {
        if(i == first)
            cout << i;
        else
            cout << ' ' << i;
    }
    return 0;
}

(个人贴)本周acm培训题目题解和踩坑记录_玩家交流_4399活动中心

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值