2023年中国传媒大学程序设计大赛 题解

目录

A. ACM(签到)

思路:

代码

B. 贪吃的Diana(签到)

思路

代码

C. 神河霓朝纪(贪心)

思路:

代码

D. 穿袜子(模拟)

思路:

代码:

E. 翻转拼图(状态压缩)(搜索)

思路:

代码:

F. 舞台矩形(ST表)

思路:

代码:

J. RGB (状压tp)

思路:

代码

G. 跳台滑雪(贪心)

思路:

代码:


A. ACM(签到)

众所周知,ACM竞赛是计算机领域最具影响力和含金量的比赛,新队员小A与校队牛人小B聊了起来:

小A:师哥,如果我不坚持训练,能在ACM国家级比赛中获得奖牌吗?

小B:不太可能0.0

小A:不太可能是有多不可能?能说的具体一点吗,比如获得铜牌的概率是多少?

小B:其实我已经告诉你了0.0

输入描述:

一个非负整数n(0≤n≤10000),代表小A想知道的不坚持训练能在ACM国家级比赛中获得铜牌概率的精确位数。

输出描述:

0,精确到n位小数。

输入

10

输出

0.0000000000

思路:

签到,输入n,输出小数点后n个0

代码

#include 
using namespace std;
int main() {
    int n;
    cin>>n;
    if(n==0){
        cout<<0;
        return 0;
    }
    printf("0.");
    for(int i=0;i

B. 贪吃的Diana(签到)

题目描述

小草莓 Diana 同学是众所周知的大胃王,如果每天无法吃够总计 K 饱腹度的外卖,她就会不开心。

今天她的朋友 —— 帅气的 Queen 同学拿到了 Diana 之前 N 天点的所有外卖的外卖单,共计 S 份。  

每份外卖单上有两个数字,ni 和 ki ,分别表示这份外卖是 Diana 是在这 N 天里的 第 ni 天点的,且这份外卖饱腹度为 ki 。

现在她想要计算之前 N 天中,Diana有多少天是不开心的,你能帮帮她吗?

输入描述:

第一行输入 N 和 K 和 S,

接下来 S 行,其中第 i 行代表一份外卖单,包含两个整数 ni 和 ki,

表示 Diana 在第 ni 天,吃了一份饱腹度为 ki 的食物。

输出描述:

输出Diana 不开心的天数。

输入

10 5 5
4 6
2 5
3 4
3 1
1 1

输出

7

说明

Queen拿到了之前10天里,Diana的五份食物清单,且Diana每天需要至少吃饱腹度为 5 的食物才能开心。

其中,Diana 在第2天和第3天,都吃到了饱腹度之和为 5 的食物,而在第4天,吃到了饱腹度之和为 6 的食物。

在第1天,Diana 只吃到了饱腹度之和为 1 的食物。

在第5天,第6天,第7天,第8天,第9天,第10天,Diana 没有吃到任何食物。(所以也没有任何清单)

所以,Diana 在之前10天中,只有3天吃饱了,剩下有7天是不开心的。答案为7。

备注:

0 < N,S ≤ 105,
0 ≤ K ≤ 109,
0 < ni ≤ N,
0 ≤  ki ≤ 104

思路

签到,哈希,读入每天的食物后,依次和k比较即可

代码

#include 
using namespace std;
typedef long long ll;
ll n,k,s;
ll a[1000005];
int main() {
    cin>>n>>k>>s;
    while(s--){
        ll m,t;
        cin>>m>>t;
        a[m]+=t;
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        if(a[i]

C. 神河霓朝纪(贪心)

题目描述

万智牌当中有两种牌。

  • 一种叫 地牌。对于每张你场上的地牌,你可以横置它——这会为你产生 特定颜色的法术力

  • 另一种叫 咒语牌。对于每张咒语,你需要支付特定颜色的法术力作为费用,才能施放这张咒语。

而法术力总共有五种不同的颜色,白,蓝,黑,红,绿。

具体来说,地牌具有如下效果。当地牌被横置时,可以产生一点卡上标注的颜色的法术力。(例如下图,便是五张不同颜色的单色地牌)

2023年中国传媒大学程序设计大赛 题解_第1张图片

而咒语牌上标注的费用一定由如下两种组成。

  • 特定颜色的法术力 若干点。

  • 任意颜色法术力 若干点。

例如 左下图,指的是该咒语费用为,一点任意颜色的法术力,和四点黑色法术力

例如 右下图,指的是该咒语费用为,三点任意颜色的法术力,和一点白色法术力,一点黑色法术力

2023年中国传媒大学程序设计大赛 题解_第2张图片

现在给定你场上的若干张地牌(每张地牌都可以被横置一次,且最多可以被横置一次)

给定若干张咒语,问你是否能打出所有咒语。

输入描述:

第一行是一个整数T,代表测试数据组数。

对于每组数据:

第一行是一个整数 N,代表你有多少张地牌。

接下来 N 行,每行一个字符串,字符串包含一个大写字母,表示地牌的颜色。共有五种可能的大写字母,W U B G R,分别代表 白 蓝 黑 绿 红。

接下来一行,一个整数M,代表你有多少张咒语牌。

接下来 M 行,每行一个字符串(最长不超过10),字符串包含若干个大写字母,代表咒语的费用及颜色。共有六种可能的大写字母 W U B G R O,分别代表 白 蓝 黑 绿 红 及无色(即任意颜色)。

输出描述:

对于每组测试数据:

一个整行——如果你的地牌足以支付所有的咒语,输出 “YES”。否则输出 “NO”。

输入

7
2
W
W
1
WW
4
U
W
W
B
2
BUO
W
2
R
B
1
ROO
3
B
U
R
1
RRU
3
R
U
R
2
RR
O
3
U
R
B
2
UB
R
4
R
U
U
W
4
R
O
W
G

输出

YES
YES
NO
NO
YES
YES
NO

备注:

数据保证,0 < T ≤ 200,0 < N ≤ 10000,0 < M ≤ 1000。

对于每个 地牌 字符串,保证为长度为 1 的字符串,且只会包含W U B G R 五种可能的字符。

对于每个 咒语牌 字符串,保证其长度不小于1,不大于10,且只会包含 W U B G R O 六种可能的字符。

思路:

简单贪心,把所有必需的牌统计起来,优先使用必需牌,最后看剩余的牌够不够O的个数

代码

#include 
using namespace std;
typedef long long ll;
int a[1000],num[1000];
int main() {
    int T;
    cin>>T;
    while(T--){
        memset(a,0,sizeof(a));
        memset(num,0,sizeof(num));
        int n,m;
        cin>>n;
        while(n--){
            char ch;
            cin>>ch;
            num[ch]++;    //统计已有个数
        }

        cin>>m;
        bool flag=1;
        while(m--){
            string s;
            cin>>s;
            for(int i=0;i=a['W'])num['W']-=a['W'];    //比较必需牌是否充足
            else flag=0;
            if(num['U']>=a['U'])num['U']-=a['U'];
            else flag=0;
            if(num['B']>=a['B'])num['B']-=a['B'];
            else flag=0;
            if(num['G']>=a['G'])num['G']-=a['G'];
            else flag=0;
            if(num['R']>=a['R'])num['R']-=a['R'];
            else flag=0;
            if(num['W']+num['U']+num['B']+num['G']+num['R']

D. 穿袜子(模拟)

题目描述

小K同学有一箱袜子,这些袜子各自有着不同的图案,并且有的袜子只能左脚穿、有的袜子只能右脚穿、还有的袜子左右脚都能穿。每天早上他都必须从箱子里拿出1对配套的袜子穿着出门(配套的袜子必须是图案相同的,并且左脚和右脚各有1只可以穿得上的袜子)。现在告诉你箱子里本质不同(图案不同或者左右脚适配性不同)的袜子各有多少只,小K同学想知道,他最少要从箱子里取出多少只袜子,才可以保证凑齐至少1对配套的袜子?

输入描述:

第一行输入一个正整数N,表示有多少种本质不同的袜子;

接下来的N行,每行输入P,Q,M,其中P是一个非负整数,表示袜子的图案;Q是一个字符,当Q取值为'L'时,表示袜子只适配左脚,当Q取值为'R'时,表示袜子只适配右脚,当Q取值为'*'时,表示袜子同时适配左右脚;M是一个正整数,表示这种袜子共有多少只。

(1<=N<=10^5,0<=P<=10^9,Q ∈\in∈ { 'L' , 'R' , '*' },1<=M<=10^5)

输出描述:

对于每组测试数据,输出1个答案,表示最少取出的袜子数。若无法配对成功,输出-1。

输入

3
0 L 7
0 R 3
1 * 10

输出

9

说明

取出7只图案为0的左脚袜子,取出1只图案为0的右脚袜子,再取出1只图案为1的袜子。

输入

2
10 L 5
7 R 5

输出

-1

思路:

非常ex的模拟题,把所有凑不成一对的袜子都加起来,然后+1即可

每种袜子个数为  max(左脚,右脚)+ 1(如果有 * )

代码:

#include 
using namespace std;
typedef long long ll;

struct node{    //袜子
    int p;
    char kind;
    int num;
}wazi[100005];

bool cmp(node a,node b){
    if(a.p!=b.p)return a.p>n;
    for(int i=0;i>wazi[i].p>>wazi[i].kind>>wazi[i].num;
    }
    sort(wazi,wazi+n,cmp);

    bool flag=0;
    for(int i=0;i1)flag=1;    //考虑只有*的情况    
        if(wazi[i].p==wazi[i+1].p){
            flag=1;
            break;
        }
    }
    if(!flag){    //特判凑不出来
        cout<<-1;
        return 0;
    }

    ll ans=0;
    for(int i=0;i

E. 翻转拼图(状态压缩)(搜索)

题目描述

给定一个3*3的棋盘,每个格子有正反两面。现定义一种翻转操作:指定一个格子,将该格子和与其上下左右相邻(如果存在)的所有格子一同翻面。对于给定的棋盘初始状态,最少需要几步操作才能将棋盘的所有格子都翻转为正面?

输入描述:

输入一个3*3的矩阵,表示棋盘的初始状态,0表示正面,1表示反面。

输出描述:

输出一个数字,表示最少所需的操作步数。

输入

0 0 0
0 0 0
0 0 0

输出

0

说明

所有格子的初始状态已经是正面,无需翻转

输入

0 1 0
1 1 1
0 1 0

输出

1

说明

对中间格子进行1次翻转操作

输入

0 0 1
1 1 0
0 0 0

输出

2

说明

先对上方格子进行1次翻转操作,再对左上格子进行1次翻转操作

思路:

正解emm没想出来,但是一看数据范围很小,直接暴力搜索就过了

一个地方不可能翻转两次(相当于没变),所以用01串表示这九个数是否翻转,类似状态压缩,然后枚举所有状态,看是否符合条件即可

代码:

#include 
using namespace std;
typedef long long ll;

int a[5][5],b[5][5];
int ans=10000000;

int cnt(int t){        //统计这一状态的翻转次数(01串里1的个数)
    int ans=0;
    while(t){
        if(t%2==1)ans++;
        t/=2;
    }
    return ans;
}

int d[5][2]={0,0,0,1,0,-1,1,0,-1,0};    //记录相邻的四个点和本身

bool check(){        //如果翻转后全部为0,返回1
    for(int i=1;i<=3;i++){
        for(int j=1;j<=3;j++){
            if(a[i][j]%2!=0)return 0;
        }
    }
    return 1;
}

void dfs(int i){        //暴力搜索

    for(int i=1;i<=3;i++){        //先初始化数组,新开一个数组,原数组要多次操作不能动
        for(int j=1;j<=3;j++){
            a[i][j]=b[i][j];
        }
    }

    if((i>>0)%2==1){        //状态压缩,如果在第0位要翻转,相邻的位置都翻转
        int x=3,y=3;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }

    if((i>>1)%2==1){        //copy,同理,懒得推通项了
        int x=3,y=2;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }
    if((i>>2)%2==1){
        int x=3,y=1;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }
    if((i>>3)%2==1){
        int x=2,y=3;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }
    if((i>>4)%2==1){
        int x=2,y=2;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }
    if((i>>5)%2==1){
        int x=2,y=1;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }
    if((i>>6)%2==1){
        int x=1,y=3;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }
    if((i>>7)%2==1){
        int x=1,y=2;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }
    if((i>>8)%2==1){
        int x=1,y=1;
        for(int j=0;j<5;j++){
            a[x+d[j][0]][y+d[j][1]]++;
        }
    }
    if(check())ans=min(ans,cnt(i));
}

int main(){
    for(int i=1;i<=3;i++){
        for(int j=1;j<=3;j++){
            cin>>b[i][j];
        }
    }

    for(int i=0;i<(1<<9);i++){    //所有状态都考虑一遍
        dfs(i);
    }
    cout<

F. 舞台矩形(ST表)

题目描述

最近,圣翔音乐学园的99期生又要准备今年的圣翔祭了。

在一次舞台排练中,星见纯那同学意外发现,她可以将舞台视作一个平面直角坐标系。而站在舞台上的每位演员同学,都是坐标系当中一个坐标为整数的点。

星见纯那同学还发现,在布置舞台时,假如在舞台上放置一些边平行于舞台轴线的矩形,舞台会变得更加优美。她称这类矩形叫做舞台矩形。换言之,如果把舞台当作平面直角坐标系,那么四条边都平行于坐标轴的矩形便是舞台矩形。

现在星见纯那同学已经拿到了圣翔祭的台本,知道了每一位同学在舞台上的位置。现在她想要知道,对于某一对同学 A 和 B,能覆盖横坐标在A,B之间的所有同学的最小舞台矩形面积是多少。

聪明的星见纯那当然能够算出这个问题的答案,那么你可以算出来吗。

输入描述:

第一行是一个整数 N,代表舞台上演员同学的数量。

接下来N行,每行有两个整数 Xi 和 Yi,代表第 i 位演员在舞台上的坐标为 (Xi , Yi)

接下来一行,一个整数Q,代表询问组数。

接下来Q行,每行两个整数 A 和 B,代表对于该组询问,你需要找到最小满足下面条件的矩形,并输出该矩形的面积:

矩形的四条边都平行于平面直角坐标系的某一条坐标轴。

对于任意 横坐标大于等于 XA (即第 A 位演员的横坐标)且 横坐标小于等于 XB(即第 B 位演员的横坐标)的演员同学,其一定在矩形内部或在矩形的边上。

特别地,如果矩形退化成了一条线段或一个点,视作该矩形面积为 0。

输出描述:

输出 Q 行,每行一个整数 S,代表对应询问的最小矩形的面积是S。

特别地,如果矩形退化成了一条线段或一个点,视作该矩形面积为 0。

示例1

输入

7
1 1
1 -1
1 0
3 5
7 2
-2 -3
4 4
9
7 5
1 1
1 2
6 7
6 5
4 1
1 7
3 4
6 3

输出

6
0
0
48
72
0
18
12
12

备注:

横坐标范围 -1*109 ≤ Xi ≤ 1*109 

纵坐标范围 -1*109 ≤ Yi ≤ 1*109 

N ≤ 105, Q ≤ 105

思路:

基本是个st表模板题,稍微加了点细节,查询时对应下标对应了好久,二分查找要用STL,手写二分有个负数,痛苦死了

区间长度要记得储存,不然乘的是学生编号的差

st表模板这里有——ST表处理RMQ问题模板

代码:

#include 
using namespace std;
typedef long long ll;

int stmax[100005][25],stmin[100005][25];    //st表存区间最大最小值
int m,n,Log[100005];    //预处理log2[n]

void init(){    //预处理,st表模板
    
    Log[0]=-1;
    for(int i=1;i<=n;i++){
        if(i&(i-1))Log[i]=Log[i-1];
        else Log[i]=Log[i-1]+1;
    }

    for(int j=1;j<=Log[n];j++){     //st表模板
        for(int i=1;(i+(1<>n;

    for(int i=1;i<=n;i++){
        cin>>stu[i].x>>stu[i].y;
        tx[i]=stu[i].x;         //记录横坐标
    }

    sort(stu+1,stu+n+1,cmp);
    
    for(int i=1;i<=n;i++){
        a[i]=stu[i].x;      //储存排序后第i个学生对于的横坐标,供查询时使用
        stmax[i][0]=stu[i].y;       //赋值st[i][0]
        stmin[i][0]=stu[i].y;
    }

    init();     //预处理

    int Q;
    cin>>Q;
    while(Q--){

        int l,r;
        cin>>l>>r;      //先输入学生标号
        
        l=tx[l],r=tx[r];    //对应成各自坐标
        
        if(l>=r){       //特判,>是根据样例得出来的
            cout<<0<

J. RGB (状压tp)

题目描述

给你一个M*N的网格,用红绿蓝(RGB)三种颜色填充,使得任意两个相邻的格子(上下左右四个方向相邻)颜色不同,请问总共有多少种不同的涂色方案?

结果对1e9+7取模。

2023年中国传媒大学程序设计大赛 题解_第3张图片

输入描述:

第一行输入两个正整数M和N,表示网格的尺寸。

(1<=M<=8,1<=N<=5000)

输出描述:

对于每组测试数据,输出1行答案,包含1个取模后的涂色方案数。

输入

1 3

输出

12

说明

对于1*3的网格,总共有以下12种涂色方案:

RGB,RGR,RBG,RBR,GBR,GBG,GRB,GRG,BGR,BGB,BRG,BRB。

输入

2 2

输出

18

输入

3 5

输出

5118

思路:

状压dp,要用3进制存,012代表三种颜色

要全部预处理出来,把符合条件的状态放到vector里,再把两层可以相邻的状态预处理出来放到vector里,T了一发后960ms险过

代码

#include 
using namespace std;
typedef long long ll;
ll mod=1e9+7;
int m,n;

bool check1(int t){     //三进制里判断状态是否合法
    int tmp=-1;     //储存上一位的数
    for(int i=0;iv2[10005];   //存放v2[i]存和状态i相邻的状态


int main(){
    cin>>m>>n;
    int siz=pow(3,m);
    
    vectorv;   //存合法状态
    
    for(int i=0;i

G. 跳台滑雪(贪心)

题目描述

跳台滑雪是冬奥会历史上最悠久的项目之一。选手踩着滑雪板在跳台上起滑、加速,从跳台末端飞出后,身体前倾沿抛物线在空中飞行,最后落在山坡上。裁判员会根据运动员的飞行距离和动作完成情况评分。

小L同学最近喜欢上了冬奥会的跳台滑雪项目,并且参加了一场线上跳台滑雪比赛。已知跳台选手有N种可以选择的动作姿式,每种动作都有各自的成功率pi与难度分ri,如果成功则获得相应的难度分,如果失败则不得分。小L同学最后一个出场,现在他知道了M个对手的得分ci,以及自己的得分S,他希望自己在完成下一个动作后的名次能够到达前K名(可以与其他人并列第K名)。请问他应该如何选择下一个动作,才能在保证排名小于等于K的前提下,使成功率最大。

输入描述:

第一行输入三个正整数N、M、Q,分别表示可选动作姿式的数量、跳台对手的数量、以及查询的次数;

接下来的N行,每行输入一个浮点数pi和一个正整数ri,分别表示第i个动作的成功率和难度分;

接下来的一行输入M个正整数c1,…,cM,其中ci表示第i个对手目前的得分,保证每个对手的得分不同;

接下来的Q行,每行输入两个正整数S和K,分别表示小L同学目前的得分以及预期的名次。

(1<=N、M、Q<=10^5,0

输出描述:

对于每组测试数据,输出Q行答案,即对于每次查询,输出小L同学要选择的动作的下标(从0开始)。如果有多个符合条件的动作,首先选择分数最高的。如果分数和成功率都相同,选择下标最小的。如果不可能到达目标名次,输出-1。

输入

5 3 2
0.2 10
0.5 9
0.4 8
0.9 7
0.7 2
5 10 15
2 2
2 1

输出

1
-1

说明

对于第1次查询,有编号为0、1、2的三种动作可以到达前2名。如果选择编号为0的动作,成功率0.2,得分12.0,排名第2;如果选择编号为1的动作,成功率0.5,得分11.0,排名第2;如果选择编号为2的动作,成功率0.4,得分10.0,排名并列第2。所以选择编号为1的动作成功率最高。

对于第2次查询,不论选择哪个动作,最终得分都不会超过12.0分,所以名次不可能上升到第1名或者并列第1名。

思路:

贪心,优先选概率高的动作,将所有得分情况预处理出来,使查询复杂度为O(1)

把所有动作按概率排序,枚举一遍
当前动作能获得的分数和之前动作获得的最高分之间的分数  要做的动作为当前动作

代码:

#include
using namespace std;
typedef long long ll;
int n,m,q;
int a[1000005];
struct ty{
    double p;
    int r;
    int num;
}node[100005];

bool cmp(ty a,ty b){
    if(a.p==b.p)return a.r>b.r;     //其次按分数排序
    return a.p>b.p;     //按概率排序
}

int dp[1000005];
int max0=0;     //记录最大的可能得分

int main(){
    
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++){
        cin>>node[i].p>>node[i].r;      //读入每个动作
        max0=max(max0,node[i].r);
        
        assert(node[i].r>0);        //注意得分可能等于0,题目数据有误,被WA得死去活来
        node[i].num=i-1;
    }
    
    stable_sort(node+1,node+1+n,cmp);   //保证序号有小到大
    
    int t=-1;
    for(int i=1;i<=n;i++){
            
        for(int j=node[i].r;j>t;j--){
            dp[j]=node[i].num;     //预处理每个得分的情况
        }

        t=max(node[i].r,t);     //记录当前的最大得分
    }

    for(int i=1;i<=m;i++){
        cin>>a[i];
    }
    
    sort(a+1,a+1+m,greater());
    /*for(int i=1;i<=n;i++){
        cout<>s>>k;
        int t=a[k]-s;       //最少需要的分数
        
        if(t<0){        //特判一定能到达
            cout<max0){     //特判
            cout<<-1<

你可能感兴趣的:(比赛题解汇总,c++,算法,开发语言)