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

文章目录

文章目录

    • 题单传送门
      • [P3156 【深基15.例1】询问学号 - 洛谷](https://www.luogu.com.cn/problem/P3156)
      • [P3613 【深基15.例2】寄包柜 - 洛谷](https://www.luogu.com.cn/problem/P3613)
      • [P1449 后缀表达式 - 洛谷](https://www.luogu.com.cn/problem/P1449)
      • [P1996 约瑟夫问题 - 洛谷](https://www.luogu.com.cn/problem/P1996)
      • [P1160 队列安排 - 洛谷](https://www.luogu.com.cn/problem/P1160)
      • [P1540 [NOIP2010 提高组] 机器翻译 - 洛谷](https://www.luogu.com.cn/problem/P1540)
      • [P2058 [NOIP2016 普及组] 海港 - 洛谷](https://www.luogu.com.cn/problem/P2058)
      • [P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷](https://www.luogu.com.cn/problem/P1090)
      • [P1241 括号序列 - 洛谷](https://www.luogu.com.cn/problem/P1241)
      • [P4387 【深基15.习9】验证栈序列 - 洛谷](https://www.luogu.com.cn/problem/P4387)
      • [P2234 [HNOI2002]营业额统计 - 洛谷](https://www.luogu.com.cn/problem/P2234)

题单传送门


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

此题较为简单,只需开一个数组即可。但是鄙人想用STL,所以就用了vector。

#include 
#include 
#include 
#include 

using namespace std;

int main()
{
    int n, m;
    cin >> n >> m;
    
    vector<int> v;
    for(int i = 1; i <= n; i ++)
    {
        int x;
        cin >> x;
        v.push_back(x);
    }
    
    for(int i = 1; i <= m; i ++)
    {
        int x;
        cin >> x;
        cout << v[x - 1] << endl;
    }
    
    return 0;
}

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

首先,肯定不能简单的开一个二维数组;因为二维数组一定是连续的内存,显然会空间超限。

对于每一次存放操作,建立一次二维映射,对于每一次查询操作,直接输出其映射的值。

可以使用STL中的Map来进行数据离散化。带上一个 loglog 的时间复杂度能接受。

在此题中,我们使用cabinet[i][j]来表示第 i i i个柜子的第 j j j个物品存放的东西(0表示无东西存放)。

#include 
#include 
#include 
#include 
#include 

using namespace std;

int main()
{
    int n, q;
    cin >> n >> q;
    
    map<int, map<int, int>> cabinet;         //建立二维映射
    for(int i = 1; i <= q; i ++)
    {
        int x;
        cin >> x;
        
        if(x == 1)
        {
            int i, j, k;
            cin >> i >> j >> k;
            cabinet[i][j] = k;              //建立一次映射
        }
        else
        {
            int i, j;
            cin >> i >> j;
            cout << cabinet[i][j] << endl;
        }
    }
    
    return 0;
}

P1449 后缀表达式 - 洛谷

这一题可以直接在读的时候判,并不需要什么字符串,+和*没有什么可说的,重点是-和/,是n[top-1]-n[top],原因很简单,就是后缀表达式里的定义。遇见运算符,直接找到栈顶的两个元素,弹出它们,并进行运算后将运算结果压入栈中。

栈的模板题。主要是如何读取数字,因为这里的数字可能不只一位。这里可以边读边判。

#include 
#include 
#include 
#include 

using namespace std;

int main()
{
    stack<int> stk;
    char op;
    int num = 0, x, y;
    while((op = getchar()) != '@')    //这里记得加括号,否则出错
    {
        switch(op)
        {
                case '.':
                {
                    stk.push(num);
                    num = 0;
                    break;                //记得加break,以下同
                }
                case '+':
                {
                    x = stk.top();
                    stk.pop();
                    y = stk.top();
                    stk.pop();
                    stk.push(x + y);
                    break;
                }
                case '-':
                {
                    x = stk.top();
                    stk.pop();
                    y = stk.top();
                    stk.pop();
                    stk.push(y - x);
                    break;
                }
                case '*':
                {
                    x = stk.top();
                    stk.pop();
                    y = stk.top();
                    stk.pop();
                    stk.push(x * y);
                    break;
                }
                case '/':
                {
                    x = stk.top();
                    stk.pop();
                    y = stk.top();
                    stk.pop();
                    stk.push(y / x);
                    break;
                }
                default :         //到这里说明是数字
                {
                    num = num * 10 + op - '0';
                    break;
                }
        }
    }
    
    cout << stk.top() << endl;
    
    return 0;
}

P1996 约瑟夫问题 - 洛谷

首先我们需要模拟一个队列,将所有的元素压进队列。在进行循环(直到队列为空为止) 首先你要知道:队列只可以在head删除,那么这就要求我们只要这个人经过判断并且不会被剔除,那么就必须把他排在队尾;若这个人正好被剔除,那先输出他,再踢除。

首先初始化队列和count,每次循环看队头,如果count已经等于m了,将其删除,并令count等于1,如果不等于m,将其压入队尾,如此循环往复。

以下来自大佬绝顶我为峰的题解博客:

.p=(p+n%m-1+m)%m;很多人问这是什么,我给大家解释一下,其实所有约瑟夫杀人问题都可以套这个公式。

p相当于指针,指向下一个要被杀的人;n%m是由于n可能会比m打,为了减小运算量,对他先取余;再加上p是由于这一回要从p的位置开始数,所以+p;减去1是因为p本身也数,我们多数了一个,所以减去1;只要有减法就可能会出现负数,防止越界要再多数一圈,也就是加m;最后再对m取余,得出p——下个被杀出圈的人。

#include 
#include 
#include 
#include 

using namespace std;

int main()
{
    int n, m;
    cin >> n >> m;
    
    queue<int> q;
    for(int i = 1; i <= n; i ++)
        q.push(i);
    
    int count = 1;          //这里是1,而不是0
    while(!q.empty())
    {
        if(count == m)      //等于m直接删除
        {
            cout << q.front() << " ";
            q.pop();
            count = 1;      //这里是1,而不是0
        }
        
        q.push(q.front());
        q.pop();
        count ++;
    }
    
    return 0;
}

P1160 队列安排 - 洛谷

前置知识:insert(it, val)成员函数用于在链表中插入元素。it为该链表的一个迭代器,val为待插入的值,插入后val位于it所指位置的前一位。返回值为一个迭代器,表示val插入到了哪个位置。

本题关键在于插入和删除元素的操作。插入可用insert函数来做,删除可用erase函数来做;另一个难点是要存储每个元素在数据结构中的位置,这里用了一个迭代器数组来存储每一个元素的位置。值得一提的是,insert函数插入后还可以返回插入的位置;next函数可以返回下一个迭代器;还要开一个erase数组存储每个元素是否从数据结构中被删除。

#include 
#include 
#include 
#include 

using namespace std;

const int N = 1e5 + 10;

bool erase[N];          //erase[k]记录值为k的元素是否从list中被删除,全局变量初始化为false

int main()
{
    int n;
    cin >> n;
    
    list<int> line;         //用一个list存储所有元素
    list<int>::iterator pos[N];   //pos[k]代表数值为k的元素的迭代器值
    
    line.push_back(1);         //初始化
    pos[1] = line.begin();
    
    for(int i = 2; i <= n; i ++)
    {
        int k, p;
        cin >> k >> p;
        if(p == 0)
            pos[i] = line.insert(pos[k], i);  //插左边
        else
        {
            auto next_iter = next(pos[k]);   //得到值为k的下一个位置,即右边
            pos[i] = line.insert(next_iter, i);  //插右边
            
        }
    }
    
    int m;
    cin >> m;
    while(m --)
    {
        int x;
        cin >> x;
        if(!erase[x])      //没被删过
            line.erase(pos[x]);
        erase[x] = true;   //记录下来,已删除
    }
    
    for(auto i : line)
        cout << i << " ";
    
    return 0;
}

P1540 [NOIP2010 提高组] 机器翻译 - 洛谷

软件会清空最早进入内存的那个单词”这句话提醒我们要使用队列,而不能使用栈;因为队列可以删除队头,而栈不能。

整体思路也较为简单:

用队列q充当内存,可存入m个单词。输出一个数x,用exist[x]检测x是否存在于队列q中,如果存在不用管,直接下一个;如果不存在,将其加入队列中,同时ans++(ans用来记录需要查询外存的次数,初始化为0),然后检测队列元素的个数有没有超过m,如果超过,就删除队头;没有超过,则继续。如此循环往复。

#include 
#include 
#include 
#include 

using namespace std;

const int N = 1e3 + 10;

bool exist[N];        //erase[k]记录值为k的元素是否从list中被删除,全局变量初始化为false

int main()
{
    int m, n;
    cin >> m >> n;
    
    int ans = 0;
    queue<int> q;
    while(n --)
    {
        int x;
        cin >> x;
        if(!exist[x])   //检测队列里是否存在x,如果不存在则符合条件
        {
            ans ++;     //内存里不存在x,需要查询
            q.push(x);  //压入队列
            exist[x] = true;  //记录队列里存在x
            if(q.size() > m)  //内存是否超限,是则删除队头
            {
                exist[q.front()] = false;  //这一步不要忘了,删除后,队列里就不存在它了
                q.pop();
            }
        }
    }
    
    cout << ans << endl;
    
    return 0;
}

P2058 [NOIP2016 普及组] 海港 - 洛谷

感谢大佬Md_Drew的博客题解。

这道题关键在于数人头。关键词是: ∑ k i ≤ 3 × 1 0 5 \sum {k_i} \le 3 \times 10^5 ki3×105。这意味着总人数就这么多,我们完全可以用count_nation[M]来表示每个nation的人数,通过队列time和nation来记录每个人来的时间和所属国家。因为题目要求船到岸的时间是升序的,所以我们不用自己排序。假设现在船到了,输入参数,我们先输入时间和人数。根据时间,把不在24小时之内的人全部删掉,需要更新队列time和nation以及count_nation数组和ans,之后再添加这只船的所有人,也要更新以上四个参数。最后输出结果。

#include 
#include 
#include 
#include 

using namespace std;

const int N = 3e5 + 10, M = 1e5 + 10;

int ans = 0;
queue<int> tm, nation; 
int count_nation[M];

int main()
{
    int n;
    cin >> n;
    
    while(n --)
    {
        int t, k;
        cin >> t >> k;
        while(!tm.empty())     //防止刚开始队列为空时出错 !记得要先删减后增加!
        {
            if(tm.front() + 86400 <= t)   //满足条件说明要删减
            {
                int j = nation.front();
                count_nation[j] --;       //相应的国家人数减一  
                if(!count_nation[j])      //如果为0,结果就减一
                    ans --;
                tm.pop(), nation.pop();   //要弹一起弹
                continue;                 //这个不可少
            }
            break;                        //及时结束
        }
            
        while(k --)                       //现在开始输入
        {
            int x;
            cin >> x;
            tm.push(t), nation.push(x);   //要压一起压
            if(!count_nation[x])
                ans ++;                   //及时更新国家人数和结果
            count_nation[x] ++;
        }
        
        cout << ans << endl;
    }
    
    return 0;
}

其实也完全可以将time和nation两个参数弄成一个结构体来操作,也是可以的。

P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷

这里有一道优先队列(堆)的模板题,同时有一点贪心的思想。思路是定义一个小根堆,每次取前两个数(必定是堆中最小的两个数),弹出它们,把它们的和加入到ans中,同时压入堆中,如此循环往复。

#include 
#include 
#include 
#include 

using namespace std;

const int N = 3e5 + 10, M = 1e5 + 10;

int ans = 0;
queue<int> tm, nation; 
int count_nation[M];

int main()
{
    int n;
    cin >> n;
    
    priority_queue< int, vector<int>, greater<int> > fruit;     //书写格式要这样
    while(n --)
    {
        int x;
        cin >> x;
        fruit.push(x);
    }
    
    int ans = 0;
    while(fruit.size() > 1)   //注意这里的条件
    {
        int a, b;
        a = fruit.top(), fruit.pop();
        b = fruit.top(), fruit.pop();
        ans += a + b;
        fruit.push(a + b);
    }
    
    cout << ans << endl;
    
    return 0;
}

P1241 括号序列 - 洛谷

感谢大佬YuJieSong的指点迷津,这道题的题意都没看明白。

题意大概是:输入一个字符串,从左往右遍历每个字符,碰见右括号( )] 都算是右括号)就往左遍历寻找最近的未匹配的左括号( ([ 都算是左括号),如果找到了,那么把这两个左右括号都标记为已匹配;如果没找到,或者左右括号并不匹配(两者是 (] 或者 )[),就算了,继续看下一个字符。最后得到了每个位置上字符是否被标记的数组。

输出的时候,遍历每一个字符,如果当前字符未匹配,则自行将其配对并输出;如果当前字符已匹配,直接输出即可。继续看下一个字符。对了,补充个冷知识:() ASCII码仅相差1,而 [] 相差2。

#include 
#include 
#include 

using namespace std;

const int N = 110;

bool match[N];     //标记每个字符是否已经匹配

int main()
{
    string s;
    cin >> s;
    for(int i = 0; s[i]; i ++)
    {
        if(s[i] == ')' || s[i] == ']')    //发现是右括号
        {
            for(int j = i - 1; s[j]; j --)   //开始往左遍历
            {
                if((s[j] == '(' || s[j] == '[') && !match[j])   //满足条件即为未匹配的最近左括号
                {
                    if(s[j] + 1 == s[i] || s[j] + 2 == s[i])    //看两者是否匹配
                        match[j] = match[i] = true;             //二者匹配,则都做标记    
                    break;                                      //不要继续找了,直接看下一个字符 
                }
            }
        }
    }
    
    for(int i = 0; s[i]; i ++)
    {
        if(match[i])      //已匹配,直接输出后看下一个字符
        {
            cout << s[i];
            continue;
        }
        if(s[i] == '(' || s[i] == ')')   //分两种括号情况,自行配对后输出
            cout << "()";
        else
            cout << "[]";
    }
    
    return 0;
}

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

据大佬说,本题是模拟栈的题目。本题自己理解出现了错误:弹出栈时可能会连续弹出多个,而不是只弹出一个。比如可能会这样:(1, 2, 3, 4) -> (3, 2, 1, 4)。所以不要被样例给误导了。

所以思路是:对于每次查询,先用两个数组 a a a b b b 存储两组数据,然后进行模拟栈的循环。每次都将原始数据 a 数组的一个数(从左到右)压入栈,判断是否与目前 b 数组的值相同,如果相同,则弄一个弹出栈的循环,每次将栈顶的数与 b 数组下标 p o s pos pos 指向的数比较,如果相同,则弹出栈,b 数组下标 p o s pos pos 也加一;一直循环比较,直到两个数不同时停止。如果 a 数组的那个数与 b 数组的数不同,则继续循环。如此循环往复。

#include 
#include 
#include 
#include 

using namespace std;

const int N = 1e5 + 10;

int main()
{
    int q;
    cin >> q;
    
    while(q --)
    {
        int n;
        cin >> n;
        
        int a[N], b[N];                //先将两行数据存起来
        for(int i = 1; i <= n; i ++)
            cin >> a[i];
        for(int i = 1; i <= n; i ++)
            cin >> b[i];
        
        stack<int> stk;
        int pos = 1;
        
        for(int i = 1; i <= n; i ++)
        {
            stk.push(a[i]);           //压入数组中
            while(stk.top() == b[pos] && !stk.empty())   //如果当前两数相等
            {
                pos ++;                    //指向下一个
                stk.pop();                 //弹出
            }
        }
        
        if(stk.empty())                //为空说明弹出顺序合理
            cout << "Yes" << endl;
        else 
            cout << "No" << endl;
    }
    
    return 0;
}

P2234 [HNOI2002]营业额统计 - 洛谷

感谢大佬Okarin的博客题解。本题思路如下:

set能有序地维护同一类型的元素,但相同的元素只能出现一次。对于这道题来说,我们可以用set来记录下之前出现过的所有营业额。每次输入一个新的数x后,通过lower_bound操作找到set中大于等于x的第一个数。

对于输入的一个数,有以下几种情况:

0.如果这是第一个数,直接插入到set里。

1.这个数等于x,显然最小波动值为0,我们也不需要再插入一个x放到set里了。

2.这个数大于x,通过set的特性可以很轻松的找到这个数的前驱,也就是小于x的第一个数。将两个数分别减去x,对绝对值取个min就好了。此时要将x插入到set中。

#include 
#include 
#include 
#include 

using namespace std;

const int INF = 0x3f3f3f3f;

int main()
{
    int n;
    cin >> n;
    
    set<int> s;      //这个初始化的操作很重要,有大佬是这么说的:这个两个值是边界值。如果第二个数比第一个数小那么lower_bound就会找到第一个数的位置,那么这个位置-1必须也得有数据,不然a--会报错。
    s.insert(INF);
    s.insert(-INF);
    
    int x, ans = 0;
    cin >> x;     //第一个数,直接插入,并且加入ans中
    s.insert(x);
    ans += x;
    for(int i = 2; i <= n; i ++)
    {
        cin >> x;
        set<int>::iterator pos = s.lower_bound(x);    //得到大于等于x的第一个地址
        if(*pos > x)
            //ans += abs(* pos - x) > abs(*(pos --) - x) ? abs(*(pos --) - x) : abs(*pos - x); 错误代码
            ans += min(abs(* pos - x), abs(*(pos --) - x));
        s.insert(x);
        //如果两者等于的话,加的是0,所以啥都不用做
    }
    
    cout << ans << endl;
    
    return 0;
}

你可能感兴趣的:(数据结构,数据结构,c++,算法)