AT_abc411_d 题解

题目传送门
洛谷
ATCoder

题目描述

有一个服务器和 N 台 PC,编号为 1,2,3,..., N。服务器和每台 PC 各自持有一个字符串,初始时所有字符串都为空。

给出 Q 个查询。每个查询是以下格式之一:

1 p:将 p 号 PC 的字符串的替换为服务器的字符串。

2 p s:将字符串 s 加到 PC 中的字符串 p 的的末尾。

3 p:将服务器的字符串替换为 p 号 PC 的字符串。

按照给定的顺序处理所有查询后,找出服务器的最终字符串。

数据范围:

1 <= N, Q <= 2*10^5
对于每个查询,1 <= p <= N。
对于每个 2 类型的查询,s 是一个长度至少为 1 的字符串,由小写英文字母组成。
在所有类型为 2 的查询中,s 的长度总和最多为 10^6

输入格式

第一行输入两个整数 N 和 Q。

接下来的 Q 行:每行一个查询,格式如上所述。

输出格式

输出服务器的最终字符串。

思路

首先,我们可以按照题目要求模拟,这个非常简单,不必多讲,就是利用了STL string

#include <iostream>
using namespace std;
string pc[200001]; // PC
string server; // 服务器
int main()
{
    int n, q;
    cin >> n >> q;
    while (q--)
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            int p;
            cin >> p;
            pc[p] = server; // 按照题目模拟
        }
        else if (op == 2)
        {
            int p;
            string s;
            cin >> p >> s;
            pc[p] += s; // 按照题目模拟(可以利用STL string 拼接的功能)
        }
        else
        {
            int p;
            cin >> p;
            server = pc[p]; // 按照题目模拟
        }
    }
    cout << server << endl;
    return 0;
}

这份代码短小精悍,获得了以下的好成绩

嘶—— 为什么呢?这份代码似乎已经达到了最快的效果了啊!

哦!!!字符串的赋值与拼接都是 O(size) 的,肯定会超时啊!

嘶—— 那这怎么办?

哦!!!我们可以先把文本中出现的字符串用数组存起来,pc和服务器就只需要存储数组中字符串的下标,在做赋值操作时就可以接近O(1)了!

等一下!那拼接的时候怎么记住这个字符串拼接到了这里?

嘶—— 那怎么办呢?

哦!!!我们可以记住这个字符串的上一段字符串的编号,然后pc的下标就改为这个字符串的编号。
当需要遍历整个字符串时,就可以一步一步往上跳,把每一段的字符串接在一起,就可以了。
所以现在一个下标就能表示一个字符串。

于是我们就能写出代码:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int pc[200001];
int server;
string str[200001];
int fa[200001];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    cin >> n >> q;
    int cnt = 0;
    for (int i = 1; i <= q; i++)
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            int p;
            cin >> p;
            pc[p] = server; // 将下标赋值给server,因为一个下标就能表示一个字符串。
        }
        else if (op == 2)
        {
            cnt++;
            int p;
            cin >> p >> str[cnt];
            fa[cnt] = pc[p]; // 记住这个字符串之前的下标
            pc[p] = cnt; // 更新下标
        }
        else
        {
            int p;
            cin >> p;
            server = pc[p]; // 将下标赋值给pc[p],因为一个下标就能表示一个字符串。
        }
    }
    int p = server; // 当前下标
    string ans;
    while (p != 0) 
    {
        ans = str[p] + ans; // 每一次在头上加上前面的字符串(不在后面加,要在头上加,因为越往前跳,这个字符串是越早加的)
        p = fa[p]; // 往前跳
    }
    cout << ans << endl;
    return 0;
}

你以为结束了?没有!这份代码获得了下面的好成绩:

嘶—— 咋又出问题了?这份代码似乎已经无懈可击了啊!

究竟是哪里出问题了?

哦!!!输出的时候又出现了赋值和拼接!每一次拼接都使字符串变得更长,导致赋值操作一次更比一次慢!

嘶—— 那这怎么办?

那我们可以直接输出吗?

不行!字符串得从前往后输出,也就是说后跳到的先输出!不能先跳到的直接输出!

哦!!!那把跳到的字符串依次先用vector存起来,然后将vector从后往前输出就可以了。

可是那也要赋值啊!

不用担心!这次赋值之后没有拼接,不会导致每一次都使字符串更长。也就是说耗时最多也只能是所有短字符串长度之和(题目中说了 <= 10^6)

那就可以放心的写代码了:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int pc[200001];
int server;
string str[200001];
int fa[200001];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    cin >> n >> q;
    int cnt = 0;
    for (int i = 1; i <= q; i++)
    {
        int op;
        cin >> op;
        if (op == 1)
        {
            int p;
            cin >> p;
            pc[p] = server; // 将下标赋值给server,因为一个下标就能表示一个字符串。
        }
        else if (op == 2)
        {
            cnt++;
            int p;
            cin >> p >> str[cnt];
            fa[cnt] = pc[p]; // 记住这个字符串之前的下标
            pc[p] = cnt; // 更新下标
        }
        else
        {
            int p;
            cin >> p;
            server = pc[p]; // 将下标赋值给pc[p],因为一个下标就能表示一个字符串。
        }
    }
    int p = server;
    vector<string> ansv; // 存储小字符串
    while (p != 0)
    {
        ansv.push_back(str[p]); // 依次放入
        p = fa[p];
    }
    for (int i = ansv.size() - 1; i >= 0; i--) // 倒序输出
    {
        cout << ansv[i];
    }
    cout << endl;
    return 0;
}

这份代码获得了以下的好成绩:

而且由于最根本的问题解决了,所以运行速度飙到了39ms

但是!真结束了吗?
确实结束了,点个赞呗!

感谢大佬们可以看完蒟蒻又臭又长的题解。

posted @ 2025-06-23 22:42  MichaelZeng  阅读(27)  评论(0)    收藏  举报