ccf-csp 2016秋季真题题解

本文精选了四个经典算法挑战题目,包括股票波动分析、火车座位分配、炉石传说游戏模拟及交通规划问题,深入解析了每道题目的背景、输入输出格式及样例说明,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


  1. 最大波动
    问题描述
      小明正在利用股票的波动程度来研究股票。小明拿到了一只股票每天收盘时的价格,他想知道,这只股票连续几天的最大波动值是多少,即在这几天中某天收盘价格与前一天收盘价格之差的绝对值最大是多少。
    输入格式
      输入的第一行包含了一个整数n,表示小明拿到的收盘价格的连续天数。
      第二行包含n个正整数,依次表示每天的收盘价格。
    输出格式
      输出一个整数,表示这只股票这n天中的最大波动值。
    样例输入
    6
    2 5 5 7 3 5
    样例输出
    4
    样例说明
      第四天和第五天之间的波动最大,波动值为|3-7|=4。
    评测用例规模与约定
      对于所有评测用例,2 ≤ n ≤ 1000。股票每一天的价格为1到10000之间的整数。

代码:

#include <iostream>
#include <algorithm>

using namespace std;

int main(){
    int ans = 0, a, b, n;

    cin >> n;
    for(int i = 0; i < n; i ++){
        cin >> a;
        //从第二项开始计算
        if(i){
            ans = max(ans, abs(a - b));
        }
        //b 为前一项
        b = a;
    }
    cout << ans << endl;
    return 0;
}

  1. 火车购票
    问题描述
      请实现一个铁路购票系统的简单座位分配算法,来处理一节车厢的座位分配。
      假设一节车厢有20排、每一排5个座位。为方便起见,我们用1到100来给所有的座位编号,第一排是1到5号,第二排是6到10号,依次类推,第20排是96到100号。
      购票时,一个人可能购一张或多张票,最多不超过5张。如果这几张票可以安排在同一排编号相邻的座位,则应该安排在编号最小的相邻座位。否则应该安排在编号最小的几个空座位中(不考虑是否相邻)。
      假设初始时车票全部未被购买,现在给了一些购票指令,请你处理这些指令。
    输入格式
      输入的第一行包含一个整数n,表示购票指令的数量。
      第二行包含n个整数,每个整数p在1到5之间,表示要购入的票数,相邻的两个数之间使用一个空格分隔。
    输出格式
      输出n行,每行对应一条指令的处理结果。
      对于购票指令p,输出p张车票的编号,按从小到大排序。
    样例输入
    4
    2 5 4 2
    样例输出
    1 2
    6 7 8 9 10
    11 12 13 14
    3 4
    样例说明
      1) 购2张票,得到座位1、2。
      2) 购5张票,得到座位6至10。
      3) 购4张票,得到座位11至14。
      4) 购2张票,得到座位3、4。
    评测用例规模与约定
      对于所有评测用例,1 ≤ n ≤ 100,所有购票数量之和不超过100。

代码:

#include <iostream>

using namespace std;

bool vis[110];

int main(){
    int n, a;

    cin >> n;
    while(n --){
        cin >> a;
        bool op = false;
        //遍历20排
        for(int i = 0; i < 20 && !op;  i++){
        	//第 i 排第一列的座位号
            int j = i * 5;
            while(j < (i + 1) * 5){
            	//找到第一个空位跳出循环
                if(!vis[j])
                    break;
                j ++;
            }
			//第i排剩余空位的长度大于等于a
            if((i + 1) * 5 - j >= a){
                while(a){
                    a --;
                    vis[j] = true;
                    cout << j + 1 << " ";
                    j ++;
                }
                //op为true代表已经分配完成了
                op = true;   
            }
        }
        //未分配 只能跨行分配座位
        if(!op){
            for(int i = 0; i < 100 && a;  i++){
                if(vis[i])
                    continue;
                vis[i] = true;
                a --;
                cout << i + 1 << " ";
            }
        }
        cout << endl;
    }

    return 0;
}

  1. 炉石传说
    问题描述
      《炉石传说:魔兽英雄传》(Hearthstone: Heroes of Warcraft,简称炉石传说)是暴雪娱乐开发的一款集换式卡牌游戏(如下图所示)。游戏在一个战斗棋盘上进行,由两名玩家轮流进行操作,本题所使用的炉石传说游戏的简化规则如下:
    在这里插入图片描述
      * 玩家会控制一些角色,每个角色有自己的生命值和攻击力。当生命值小于等于 0 时,该角色死亡。角色分为英雄和随从。
      * 玩家各控制一个英雄,游戏开始时,英雄的生命值为 30,攻击力为 0。当英雄死亡时,游戏结束,英雄未死亡的一方获胜。
      * 玩家可在游戏过程中召唤随从。棋盘上每方都有 7 个可用于放置随从的空位,从左到右一字排开,被称为战场。当随从死亡时,它将被从战场上移除。
      * 游戏开始后,两位玩家轮流进行操作,每个玩家的连续一组操作称为一个回合。
      * 每个回合中,当前玩家可进行零个或者多个以下操作:
      1) 召唤随从:玩家召唤一个随从进入战场,随从具有指定的生命值和攻击力。
      2) 随从攻击:玩家控制自己的某个随从攻击对手的英雄或者某个随从。
      3) 结束回合:玩家声明自己的当前回合结束,游戏将进入对手的回合。该操作一定是一个回合的最后一个操作。
      * 当随从攻击时,攻击方和被攻击方会同时对彼此造成等同于自己攻击力的伤害。受到伤害的角色的生命值将会减少,数值等同于受到的伤害。例如,随从 X 的生命值为 HX、攻击力为 AX,随从 Y 的生命值为 HY、攻击力为 AY,如果随从 X 攻击随从 Y,则攻击发生后随从 X 的生命值变为 HX - AY,随从 Y 的生命值变为 HY - AX。攻击发生后,角色的生命值可以为负数。
      本题将给出一个游戏的过程,要求编写程序模拟该游戏过程并输出最后的局面。
    输入格式
      输入第一行是一个整数 n,表示操作的个数。接下来 n 行,每行描述一个操作,格式如下:
       …
      其中表示操作类型,是一个字符串,共有 3 种:summon表示召唤随从,attack表示随从攻击,end表示结束回合。这 3 种操作的具体格式如下:
      * summon :当前玩家在位置召唤一个生命值为、攻击力为的随从。其中是一个 1 到 7 的整数,表示召唤的随从出现在战场上的位置,原来该位置及右边的随从都将顺次向右移动一位。
      * attack :当前玩家的角色攻击对方的角色 。是 1 到 7 的整数,表示发起攻击的本方随从编号,是 0 到 7 的整数,表示被攻击的对方角色,0 表示攻击对方英雄,1 到 7 表示攻击对方随从的编号。
      * end:当前玩家结束本回合。
      注意:随从的编号会随着游戏的进程发生变化,当召唤一个随从时,玩家指定召唤该随从放入战场的位置,此时,原来该位置及右边的所有随从编号都会增加 1。而当一个随从死亡时,它右边的所有随从编号都会减少 1。任意时刻,战场上的随从总是从1开始连续编号。
    输出格式
      输出共 5 行。
      第 1 行包含一个整数,表示这 n 次操作后(以下称为 T 时刻)游戏的胜负结果,1 表示先手玩家获胜,-1 表示后手玩家获胜,0 表示游戏尚未结束,还没有人获胜。
      第 2 行包含一个整数,表示 T 时刻先手玩家的英雄的生命值。
      第 3 行包含若干个整数,第一个整数 p 表示 T 时刻先手玩家在战场上存活的随从个数,之后 p 个整数,分别表示这些随从在 T 时刻的生命值(按照从左往右的顺序)。
      第 4 行和第 5 行与第 2 行和第 3 行类似,只是将玩家从先手玩家换为后手玩家。
    样例输入
    8
    summon 1 3 6
    summon 2 4 2
    end
    summon 1 4 5
    summon 1 2 1
    attack 1 2
    end
    attack 1 1
    样例输出
    0
    30
    1 2
    30
    1 2
    样例说明
      按照样例输入从第 2 行开始逐行的解释如下:
      1. 先手玩家在位置 1 召唤一个生命值为 6、攻击力为 3 的随从 A,是本方战场上唯一的随从。
      2. 先手玩家在位置 2 召唤一个生命值为 2、攻击力为 4 的随从 B,出现在随从 A 的右边。
      3. 先手玩家回合结束。
      4. 后手玩家在位置 1 召唤一个生命值为 5、攻击力为 4 的随从 C,是本方战场上唯一的随从。
      5. 后手玩家在位置 1 召唤一个生命值为 1、攻击力为 2 的随从 D,出现在随从 C 的左边。
      6. 随从 D 攻击随从 B,双方均死亡。
      7. 后手玩家回合结束。
      8. 随从 A 攻击随从 C,双方的生命值都降低至 2。
    评测用例规模与约定
      * 操作的个数0 ≤ n ≤ 1000。
      * 随从的初始生命值为 1 到 100 的整数,攻击力为 0 到 100 的整数。
      * 保证所有操作均合法,包括但不限于:
      1) 召唤随从的位置一定是合法的,即如果当前本方战场上有 m 个随从,则召唤随从的位置一定在 1 到 m + 1 之间,其中 1 表示战场最左边的位置,m + 1 表示战场最右边的位置。
      2) 当本方战场有 7 个随从时,不会再召唤新的随从。
      3) 发起攻击和被攻击的角色一定存在,发起攻击的角色攻击力大于 0。
      4) 一方英雄如果死亡,就不再会有后续操作。
      * 数据约定:
      前 20% 的评测用例召唤随从的位置都是战场的最右边。
      前 40% 的评测用例没有 attack 操作。
      前 60% 的评测用例不会出现随从死亡的情况。

代码:

#include <iostream>
#include <string>

using namespace std;

//每个单位有攻击力和血量 (英雄可以看作攻击力为0的单位)
struct node{
    int hp, att;
};
node arr[2][10];

int main(){
	//op为0是先手 为1是后手回合
    bool op = false;
    int n;
    string s;

    arr[0][0].hp = arr[1][0].hp = 30;
    arr[0][0].att = arr[1][0].att = 0;

    cin >> n;
    while(n --){
        cin >> s;
        if(s == "end")  op = !op;
        else if(s == "summon"){
            int a, b, c;
            cin >> a >> b >> c;
            //右边的随从向右边移动
            for(int i = 7; i > a; i --)
                arr[op][i] = arr[op][i - 1];
            arr[op][a].att = b;
            arr[op][a].hp = c;
        }else{
            int a, b;
            cin >> a >> b;
            arr[op][a].hp -= arr[!op][b].att;
            arr[!op][b].hp -= arr[op][a].att;
            //注意:英雄血量为0 不能移除
            if(a && arr[op][a].hp <= 0){
            	//右边的随从向左移动
                for(int i = a; i < 7; i ++)
                    arr[op][i] = arr[op][i + 1];
            }
            if(b && arr[!op][b].hp <= 0){
                for(int i = b; i < 7; i ++)
                    arr[!op][i] = arr[!op][i + 1];
            }
        }
    }
    if(arr[0][0].hp <= 0)
        cout << -1 << endl;
    else if(arr[1][0].hp <= 0)
        cout << 1 << endl;
    else
        cout << 0 << endl;

    for(int i = 0; i < 2; i ++){
        cout << arr[i][0].hp << endl;
        int cnt = 0;
        for(int j = 1; j <= 7; j ++)
            if(arr[i][j].hp > 0)
                ++ cnt;
        cout << cnt << " ";
        for(int j = 1; j <= cnt; j ++)
            cout << arr[i][j].hp << " ";
        cout << endl;
    }
    return 0;
}

  1. 交通规划
    问题描述
      G国国王来中国参观后,被中国的高速铁路深深的震撼,决定为自己的国家也建设一个高速铁路系统。
      建设高速铁路投入非常大,为了节约建设成本,G国国王决定不新建铁路,而是将已有的铁路改造成高速铁路。现在,请你为G国国王提供一个方案,将现有的一部分铁路改造成高速铁路,使得任何两个城市间都可以通过高速铁路到达,而且从所有城市乘坐高速铁路到首都的最短路程和原来一样长。请你告诉G国国王在这些条件下最少要改造多长的铁路。
    输入格式
      输入的第一行包含两个整数n, m,分别表示G国城市的数量和城市间铁路的数量。所有的城市由1到n编号,首都为1号。
      接下来m行,每行三个整数a, b, c,表示城市a和城市b之间有一条长度为c的双向铁路。这条铁路不会经过a和b以外的城市。
    输出格式
      输出一行,表示在满足条件的情况下最少要改造的铁路长度。
    样例输入
    4 5
    1 2 4
    1 3 5
    2 3 2
    2 4 3
    3 4 2
    样例输出
    11
    评测用例规模与约定
      对于20%的评测用例,1 ≤ n ≤ 10,1 ≤ m ≤ 50;
      对于50%的评测用例,1 ≤ n ≤ 100,1 ≤ m ≤ 5000;
      对于80%的评测用例,1 ≤ n ≤ 1000,1 ≤ m ≤ 50000;
      对于100%的评测用例,1 ≤ n ≤ 10000,1 ≤ m ≤ 100000,1 ≤ a, b ≤ n,1 ≤ c ≤ 1000。输入保证每个城市都可以通过铁路达到首都。

代码:

#include <iostream>
#include <cstring>
#include <climits>

using namespace std;

const int N = 10010, M = 200010;
int head[N], ver[M], edge[M], nxt[M], dist[N], q[M], cnt = 0;
bool vis[N];

void spfa(){
    memset(dist, 0x3f, sizeof(dist));
    dist[1] = 0;
    int tt = 0, hh = 0;
    q[hh ++] = 1;
    vis[1] = true;
    while(tt < hh){
        int cur = q[tt ++];
        if(tt == M)
            tt = 0;
        vis[cur] = false;
        for(int i = head[cur]; i; i = nxt[i]){
            int u = ver[i];
            if(dist[u] > dist[cur] + edge[i]){
                dist[u] = dist[cur] + edge[i];
                if(!vis[u]){
                    vis[u] = true;
                    q[hh ++] = u;
                    if(hh == M)
                        hh = 0;
                }
            }
        }
    }
}

void add(int a, int b, int c){
    ver[++ cnt] = b;
    edge[cnt] = c;
    nxt[cnt] = head[a];
    head[a] = cnt;
}

int main(){
    int a, b, c;
    int n, m;
    
    cin >> n >> m;
    while(m --){
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    
    spfa();
    
    int ans = 0;
    for(int i = 2; i <= n; i ++){
        int tmp = INT_MAX;
        for(int j = head[i]; j; j = nxt[j]){
            int u = ver[j];
            if(dist[i] == dist[u] + edge[j]){
                tmp = min(tmp, edge[j]);
            }
        }
        ans += tmp;
    }
    cout << ans << endl;
}


  1. 祭坛
    问题描述
      在遥远的Dgeak大陆,生活着一种叫做Dar-dzo-nye的怪物。每当这种怪物降临,人们必须整夜对抗怪物而不能安睡。为了乞求这种怪物不再降临,人们决定建造祭坛。
      Dgeak大陆可以看成一个用平面直角坐标系表示的巨大平面。在这个平面上,有 n 个Swaryea水晶柱,每个水晶柱可以用一个点表示。
      如果 4 个水晶柱依次相连可以构成一个四边形,满足其两条对角线分别平行于 x 轴和 y 轴,并且对角线的交点位于四边形内部(不包括边界),那么这 4 个水晶柱就可以建立一个结界。其中,对角线的交点称作这个结界的中心。
      例如下左图中,水晶柱 ABCD 可以建立一个结界,其中心为 O。
    在这里插入图片描述
      为了起到抵御Dar-dzo-nye的最佳效果,人们会把祭坛修建在最多层结界的保护中。其中不同层的结界必须有共同的中心,这些结界的边界不能有任何公共点,并且中心处也不能有水晶柱。这里共同中心的结界数量叫做结界的层数。
      为了达成这个目的,人们要先利用现有的水晶柱建立若干个结界,然后在某些结界的中心建立祭坛。
      例如上右图中,黑色的点表示水晶柱(注意 P 和 O 点不是水晶柱)。祭坛的一个最佳位置为 O 点,可以建立在 3 层结界中,其结界的具体方案见下左图。当然,建立祭坛的最佳位置不一定是唯一,在上右图中,O 点左侧 1 单位的点 P 也可以建立一个在 3 层结界中的祭坛,见下右图。
    在这里插入图片描述
      现在人们想知道:
      1. 祭坛最佳选址地点所在的结界层数;
      2. 祭坛最佳的选址地点共有多少个。
    输入格式
      输入的第一行包含两个正整数 n,q,表示水晶柱的个数和问题的种类。保证 q=1 或 2,其意义见输出格式。
      接下来 n 行,每行包含两个非负整数 x,y,表示每个水晶柱的坐标。保证相同的坐标不会重复出现。
    输出格式
      若 q=1,输出一行一个整数,表示祭坛最多可以位于多少个结界的中心;若 q=2,输出一行一个整数,表示结界数最多的方案有多少种。
    样例1输入
      26 1
      0 5
      1 1
      1 5
      1 9
      3 5
      3 10
      4 0
      4 1
      4 2
      4 4
      4 6
      4 9
      4 11
      5 0
      5 2
      5 4
      5 8
      5 9
      5 10
      5 11
      6 5
      7 5
      8 5
      9 10
      10 2
      10 5
    样例1输出
      3
    样例2输入
      26 2
      0 5
      1 1
      1 5
      1 9
      3 5
      3 10
      4 0
      4 1
      4 2
      4 4
      4 6
      4 9
      4 11
      5 0
      5 2
      5 4
      5 8
      5 9
      5 10
      5 11
      6 5
      7 5
      8 5
      9 10
      10 2
      10 5
    样例2输出
      2
    样例说明
      样例即为题目描述中的例子,两个样例数据相同,分别询问最多的结界数量和达到最多结界数量的方案数。
      其中图片的左下角为原点,右和上分别是 x 轴和 y 轴的正方向,一个格子的长度为单位长度。
      以图中的 O 点建立祭坛,祭坛最多可以位于 3 个结界的中心。不存在更多结界的方案,因此样例1的答案为 3。
      在 O 点左侧 1 单位的点 (4,5) 也可以建立一个在 3 个结界中的祭坛,因此样例2的答案为 2。
    评测用例规模与约定
      对于所有的数据,保证存在至少一种方案,使得祭坛建造在至少一层结界中,即不存在无论如何祭坛都无法建造在结界中的情况。
      数据分为 8 类,各类之间互相没有交集,分别有以下特点:
      1. 占数据的 10%,n=200,x,y≤n;
      2. 占数据的 10%,n=200,x,y≤109;
      3. 占数据的 10%,n=1000,x,y≤n;
      4. 占数据的 10%,n=1000,x,y≤109;
      5. 占数据的 10%,n=5000,x,y≤n;
      6. 占数据的 10%,n=5000,x,y≤109;
      7. 占数据的 20%,n=300000,x,y≤n;
      8. 占数据的 20%,n=300000,x,y≤109。

此外,每类数据中,q=1 与 q=2 各占恰好一半。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值