题解2020届天梯赛总决赛L2-4哲哲打游戏

引用

这道题其实就是简单的邻接表存储问题,但因为题目的原因只能采用尾插法,同一个点能到达的点可能非常多,如果全部遍历一遍再取数的话会超时,所以得另辟蹊径~刚好可以用二维vector(简化了next指针),这样就不会出现稀疏矩阵的问题了

题目描述

哲哲是一位硬核游戏玩家。最近一款名叫《达诺达诺》的新游戏刚刚上市,哲哲自然要快速攻略游戏,守护硬核游戏玩家的一切!

为简化模型,我们不妨假设游戏有 N 个剧情点,通过游戏里不同的操作或选择可以从某个剧情点去往另外一个剧情点。此外,游戏还设置了一些存档,在某个剧情点可以将玩家的游戏进度保存在一个档位上,读取存档后可以回到剧情点,重新进行操作或者选择,到达不同的剧情点。

为了追踪硬核游戏玩家哲哲的攻略进度,你打算写一个程序来完成这个工作。假设你已经知道了游戏的全部剧情点和流程,以及哲哲的游戏操作,请你输出哲哲的游戏进度。

样例
输入格式

输入第一行是两个正整数 N N N M M M ( 1 ≤ N , M ≤ 1 0 5 1 \le N , M \le 10^5 1N,M105 ),表示总共有 N N N 个剧情点,哲哲有 M M M 个游戏操作。

接下来的 N N N 行,每行对应一个剧情点的发展设定。第 i i i 行的第一个数字是 K i K_i Ki ,表示剧情点 i i i 通过一些操作或选择能去往下面 K i K_i Ki 个剧情点;接下来有 K i K_i Ki 个数字,第 k k k 个数字表示做第 k k k 个操作或选择可以去往的剧情点编号。

最后有 M M M 行,每行第一个数字是 0、1 或 2,分别表示:

  • 0 表示哲哲做出了某个操作或选择,后面紧接着一个数字 j j j ,表示哲哲在当前剧情点做出了第 j j j 个选择。我们保证哲哲的选择永远是合法的。
  • 1 表示哲哲进行了一次存档,后面紧接着是一个数字 j j j ,表示存档放在了第 j j j 个档位上。
  • 2 表示哲哲进行了一次读取存档的操作,后面紧接着是一个数字 j j j ,表示读取了放在第 j j j 个位置的存档。

约定:所有操作或选择以及剧情点编号都从1号开始。存档的档位不超过100个,编号也从1开始。游戏默认从1号剧情点开始。总的选项数(即 ∑ K i \sum_{}^{}{K_i} Ki)不超过 1 0 6 10^6 106

输出格式

对于每个 1(即存档)操作,在一行中输出存档的剧情点编号。

最后一行输出哲哲最后到达的剧情点编号。

输入样例
10 11
3 2 3 4
1 6
3 4 7 5
1 3
1 9
2 3 5
3 1 8 5
1 9
2 8 10
0
1 1
0 3
0 1
1 2
0 2
0 2
2 2
0 3
0 1
1 1
0 2
输出样例
1
3
9
10
样例说明:

简单给出样例中经过的剧情点顺序:

1 -> 4 -> 3 -> 7 -> 8 -> 3 -> 5 -> 9 -> 10。

档位 1 开始存的是 1 号剧情点;档位 2 存的是 3 号剧情点;档位 1 后来又存了 9 号剧情点。


算法

(邻接表)

根据邻接表的数组模拟实现可以发现超时的原因是找有向边的时候超时了,因为不确定有几个边,可能总的遍历次数超过 1 e 5 1e5 1e5 ~ 1 e 6 1e6 1e6 ,不能在一秒之内跑完,这是因为是头插法的原因,而且就算是想尽了一切办法实现了尾插法,且不说内存超不超,你要的目标数可能在尾部,所以时间还是有超的可能,这就是作为链表的缺点,查询是 O ( n ) O(n) O(n) 的。

如果要用邻接表、尾插法又要快速找到一个数,那么只能用数组了,而且它的实现不能与动态链表的实现一样,靠指针域去取值。如果使用纯数组实现的话,只能拿到部分分,因为这是一个稀疏矩阵,会浪费很多空间,这也是为什么用邻接表。用vector就简单了,只要连一条边就往数组第i行push一个数,i就是起点,x就是终点。( a[i].push_back(x) )

时间复杂度

鸽了

C++ 代码

尾插法(vector)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cstdio>

using namespace std;

const int N = 1e6 + 10;

int n , m;
vector<int> a[N];
int cun[110];

int main() {
    
    cin >> n >> m;
    
    int x = 0 , cnt = 0;
    for(int i = 1; i <= n; i ++) {
        scanf("%d" , &cnt);
        for(int j = 1; j <= cnt; j ++) {
            scanf("%d" , &x);
            a[i].push_back(x);
        }
    }
    
    int op = 0 , res = 1;
    for(int i = 1; i <= m; i ++) {
        scanf("%d %d" , &op , &x);
        if(op == 0) {
            res = a[res][x - 1];
        }
        else if(op == 1) {
            cout << res << endl;
            cun[x] = res;
        }
        else {
            res = cun[x];
        }
    }
    
    cout << res;
    
    return 0;
}
头插法(超时)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

const int N = 1e6 + 10;

int n , m;
int idx;
int ne[N] , e[N] , h[N];
int cun[110];

void add(int a , int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

int main() {
    
    cin >> n >> m;
    memset(h, -1, sizeof h);
    int x = 0 , cnt = 0;
    for(int i = 1; i <= n; i ++) {
        cin >> cnt;
        for(int j = 1; j <= cnt; j ++) {
            cin >> x;
            add(i , x);
        }
    }
    
    int op = 0 , res = 1;
    for(int i = 1; i <= m; i ++) {
        cin >> op >> x;
        vector<int> a;
        if(op == 0) {
            for(int j = h[res]; j != -1; j = ne[j])
                a.push_back(e[j]);

            res = a[a.size() - x];
        }
        else if(op == 1) {
            cout << res << endl;
            cun[x] = res;
        }
        else {
            res = cun[x];
        }
    }
    
    cout << res << endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值