2021/4/24团队设计天梯赛L3题目集及部分题解

2021/4/24团队设计天梯赛L3题目集及部分题解

以下题解都是通过PTA测试的,大致保证正确性:
查看题目戳此::PTA题目集

L3-01 森森旅游 (30 分)

题目描述:
好久没出去旅游啦!森森决定去 Z 省旅游一下。

Z 省有 n 座城市(从 1 到 n 编号)以及 m 条连接两座城市的有向旅行线路(例如自驾、长途汽车、火车、飞机、轮船等),每次经过一条旅行线路时都需要支付该线路的费用(但这个收费标准可能不止一种,例如车票跟机票一般不是一个价格)。

Z 省为了鼓励大家在省内多逛逛,推出了旅游金计划:在 i 号城市可以用 1 元现金兑换 a​i元旅游金(只要现金足够,可以无限次兑换)。城市间的交通即可以使用现金支付路费,也可以用旅游金支付。具体来说,当通过第 j 条旅行线路时,可以用 c​j元现金或 d​j元旅游金支付路费。注意: 每次只能选择一种支付方式,不可同时使用现金和旅游金混合支付。但对于不同的线路,旅客可以自由选择不同的支付方式。

森森决定从 1 号城市出发,到 n 号城市去。他打算在出发前准备一些现金,并在途中的某个城市将剩余现金 全部 换成旅游金后继续旅游,直到到达 n 号城市为止。当然,他也可以选择在 1 号城市就兑换旅游金,或全部使用现金完成旅程。

Z 省政府会根据每个城市参与活动的情况调整汇率(即调整在某个城市 1 元现金能换多少旅游金)。现在你需要帮助森森计算一下,在每次调整之后最少需要携带多少现金才能完成他的旅程。

输入格式:
输入在第一行给出三个整数 n,m 与 q(1≤n≤10​5,1≤m≤2×10​5,1≤q≤10^​5),依次表示城市的数量、旅行线路的数量以及汇率调整的次数。

接下来 m 行,每行给出四个整数 u,v,c 与 d(1≤u,v≤n,1≤c,d≤10​9),表示一条从 u 号城市通向 v 号城市的有向旅行线路。每次通过该线路需要支付 c 元现金或 d 元旅游金。数字间以空格分隔。输入保证从 1 号城市出发,一定可以通过若干条线路到达 n 号城市,但两城市间的旅行线路可能不止一条,对应不同的收费标准;也允许在城市内部游玩(即 u 和 v 相同)。

接下来的一行输入 n 个整数 a​1 ,a2,⋯,an(1≤a​i≤10^​9),其中 a​i表示一开始在 i 号城市能用 1 元现金兑换 a​i个旅游金。数字间以空格分隔。

接下来 q 行描述汇率的调整。第 i 行输入两个整数 x​i与 a​i​′(1≤x​i ≤n,1≤a​i​′≤10^9),表示第 i 次汇率调整后,x​i号城市能用 1 元现金兑换 a​i​′个旅游金,而其它城市旅游金汇率不变。请注意:每次汇率调整都是在上一次汇率调整的基础上进行的。

输出格式:
对每一次汇率调整,在对应的一行中输出调整后森森至少需要准备多少现金,才能按他的计划从 1 号城市旅行到 n 号城市。

再次提醒:如果森森决定在途中的某个城市兑换旅游金,那么他必须将剩余现金全部、一次性兑换,剩下的旅途将完全使用旅游金支付。

输入样例:

6 11 3
1 2 3 5
1 3 8 4
2 4 4 6
3 1 8 6
1 3 10 8
2 3 2 8
3 4 5 3
3 5 10 7
3 3 2 3
4 6 10 12
5 6 10 6
3 4 5 2 5 100
1 2
2 1
1 17

输出样例:

8
8
1

样例解释
对于第一次汇率调整,森森可以沿着 1→2→4→6 的线路旅行,并在 2 号城市兑换旅游金;

对于第二次汇率调整,森森可以沿着 1→2→3→4→6 的线路旅行,并在 3 号城市兑换旅游金;

对于第三次汇率调整,森森可以沿着 1→3→5→6 的线路旅行,并在 1 号城市兑换旅游金。

代码:看了y总的视频,思路大概就是存一个正图与一个反图,因为我们要找从1-n之间的点换代金券,那么我们就从正图中找到从1开始的单源最短路,从反图中找到从n开始的单源最短路,都可以用dijkstra算法,然后再枚举这1-n个点(注意如果第i个点到1或n不通,那么不能选择),把枚举出来的值装入multiset容器中,为什么不能装堆呢,因为我们后面需要更改他的代金券的汇率,而STL的优先队列不能更改,而multiset容器也是从小到大,底层用的红黑树效率较高,还可以更改值,最后在输出begin()对应的值就行了

#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#include <set>
using namespace std;
typedef long long LL;
typedef pair<LL,int> PII;
const int N = 100010 , M=200010*2;
const LL inf=0x3f3f3f3f3f3f3f3fll;
//链式向前星存储图
int h1[N],h2[N],e[M],ne[M],w[M],idx;
int st[N];
int n,m,q;
int radio[N];
LL dist1[N],dist2[N];
//添加有权边
void add(int h[],int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
//堆优化的dijkstra算法
void dijkstra(int h[],LL dist[],int start){
     memset(dist,0x3f,sizeof dist1);
     dist[start]=0;
     memset(st,0,sizeof st);
     priority_queue<PII,vector<PII>,greater<PII> > heap;
     heap.push({0,start});
     while(heap.size()){
         int v=heap.top().second;
         heap.pop();
         if(st[v]) continue;
         st[v]=true;
         for(int i=h[v];~i;i=ne[i]){
             int j=e[i];
             if(dist[j]>dist[v]+w[i]){
                 dist[j]=dist[v]+w[i];
                 heap.push({dist[j],j});
             }
         }
     }
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> m >> q;
    memset(h1,-1,sizeof h1);
    memset(h2,-1,sizeof h2);
    for(int i=0;i<m;i++){
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        add(h1, a, b, c);
        add(h2, b, a, d);
    }
    for(int i=1;i<=n;i++)
        cin >> radio[i];
    dijkstra(h1, dist1, 1);
    dijkstra(h2, dist2, n);
    multiset<LL> s;
    for(int i = 1;i <= n; i ++ ){
    //必须要可以连通
        if(dist1[i] != inf && dist2[i] != inf){
        //此时对于后面需要的代金券兑换的现金需要向上取整,所以要加radio[i]-1
           s.insert(dist1[i]+(dist2[i]+radio[i]-1)/radio[i]);
        }
    }
    while(q--){
        int a,b;
        cin >> a >> b;
        if(dist1[a]!=inf && dist2[a] !=inf){
        s.erase(s.find(dist1[a]+(dist2[a]+radio[a]-1)/radio[a]));
        radio[a] = b;
        s.insert(dist1[a]+(dist2[a]+radio[a]-1)/radio[a]);
        }
        cout << *s.begin()<<endl;
    }
    return 0;
}

L3-02 还原文件 (30 分)

题目描述:
一份重要文件被撕成两半,其中一半还被送进了碎纸机。我们将碎纸机里找到的纸条进行编号,如图 1 所示。然后根据断口的折线形状跟没有切碎的半张纸进行匹配,最后还原成图 2 的样子。要求你输出还原后纸条的正确拼接顺序。
图片: Alt

图片: Alt

输入格式:
输入首先在第一行中给出一个正整数 N(1<N≤10^5),为没有切碎的半张纸上断口折线角点的个数;随后一行给出从左到右 N 个折线角点的高度值(均为不超过 100 的非负整数)。

随后一行给出一个正整数 M(≤100),为碎纸机里的纸条数量。接下去有 M 行,其中第 i 行给出编号为 i(1≤i≤M)的纸条的断口信息,格式为:

K h[1] h[2] … h[K]
其中 K 是断口折线角点的个数(不超过 10^​4+1),后面是从左到右 K 个折线角点的高度值。为简单起见,这个“高度”跟没有切碎的半张纸上断口折线角点的高度是一致的。
输出格式:
在一行中输出还原后纸条的正确拼接顺序。纸条编号间以一个空格分隔,行首尾不得有多余空格。

题目数据保证存在唯一解。

输入样例:

17
95 70 80 97 97 68 58 58 80 72 88 81 81 68 68 60 80
6
4 68 58 58 80
3 81 68 68
3 95 70 80
3 68 60 80
5 80 72 88 81 81
4 80 97 97 68

输出样例:

3 6 1 5 2 4

代码:这道题目可以当做dfs来做(很多得26分的情况就是少判断了一种条件 例如 2 4 6 8 9 2 4 6,3个序列分别是:2 4 6 | 9 | 2 4 6 8当你判断到2 4 6觉得是第一个序列时,其实是2 4 6 8,但是它却是另一个序列,你并没有关心下一个是否匹配成功,就会陷入死循环),所以用dfs判断至最后一个元素时结束,当当前序列匹配成功时,对应总序列位置转移cnt+s[i].size()-1,再回溯,还需要用st数组来判断该序列是否被用过

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
//结果序列次序
vector<int> result;
//现在序列次序
vector<int> now;
int n,m;
//p存储总序列,cnt表示总序列当前判断位置,flag代表是否成功(唯一解,不必须要再判断)
int p[100005],st[105],cnt,flag;
//s存储所有碎片序列
vector<int> s[105];
void dfs(int cnt){
     if(cnt>=n-1){
        result=now;
        flag=1;
        return ;
     }
     if(flag==1)
        return ;
     for(int i=1;i<=m;i++){
         int ans=1;
         if(!st[i]){
            for(int j=0;j<s[i].size();j++){
                if(s[i][j]!=p[cnt+j]){
                     ans=0;
                     break;
                }
            }
            //ans==1表示当前序列匹配
            if(ans){
            now.push_back(i);
            st[i]=1;
            dfs(cnt+s[i].size()-1);
            st[i]=0;
            now.pop_back();
         }
         }

     }
}
int main(){
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i=0;i<n;i++) cin >> p[i];
    cin >> m;
    for(int i=1;i<=m;i++){
        int k,a;
        cin >> k;
        while(k--){
            cin >> a;
            s[i].push_back(a);
        }
    }
    memset(st,0,sizeof st);
    //从总序列的第0号元素开始
    dfs(0);
    for(int i=0;i<result.size();i++){
        if(i)
            cout << " ";
        cout << result[i];
    }
    return 0;
}

L3-03 可怜的简单题 (30 分)

题目描述:

九条可怜去年出了一道题,导致一众参赛高手惨遭团灭。今年她出了一道简单题 —— 打算按照如下的方式生成一个随机的整数数列 A:

最开始,数列 A 为空。

可怜会从区间 [1,n] 中等概率随机一个整数 i 加入到数列 A 中。

如果不存在一个大于 1 的正整数 w,满足 A 中所有元素都是 w 的倍数,数组 A 将会作为随机生成的结果返回。否则,可怜将会返回第二步,继续增加 A 的长度。

现在,可怜告诉了你数列 n 的值,她希望你计算返回的数列 A 的期望长度。

输入格式:
输入一行两个整数 n,p (1≤n≤1011,n<p≤1012),p 是一个质数。

输出格式:
在一行中输出一个整数,表示答案对 p 取模的值。具体来说,假设答案的最简分数表示为 y​x,你需要输出最小的非负整数 z 满足 y×z≡x mod p。
​​

输入样例1:

2 998244353

输出样例1:

2

输入样例2:

100000000 998244353

输出样例2:

3056898

代码:话不多说,赐教(原谅我题都没看懂),直接输出1骗1分,就猜的

#include <iostream>

using namespace std;

int main(){
    
    cout << 1;
    return 0;
}


L3的题目集到此结束,感觉今年的L3反正大家都读读吧,感觉难度适中(当然我是指追求不太大的话),如果对你有帮助留下你的赞吧!不忘初心,方得始终。

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值