武汉工程大学第三届ACM程序设计新生赛(多校联赛正式赛)题解

传送门

A 如何才能防AK

前置知识:无
题意:

统计10出现的次数,若超过6个则输出 just so so,若为1~5个则输出 good,若无则输出 666。

解法:

签到题,用一个变量cnt来统计10出现的次数,然后输出即可

Code:
#include<bits/stdc++.h>
using namespace std;
int T,cnt;//全局变量 默认为0
int main()
{
    scanf("%d",&T);
    while(T--){ //先判断T是否为0,再将T减1,相当于循环T次
        int tmp;
        scanf("%d",&tmp);
        if(tmp>=10) cnt++;
    }
    if(cnt==0) printf("666");
    else if(cnt>=6) printf("just so so");
    else printf("good");
    return 0;
}

D 战力对比

前置知识:无
题意:

给一个数列,统计其中所有长度为K的子序列中元素最大值,然后在这些最大值中找到最大值。

解法:

在所有长度为K的子序列的最大值里,一定包括整个数列中元素的最大值

若在子序列最大值中再取最大值时,即为数列的最大值。

所以直接在输入的时候找到序列的最大值即可 (甚至都没用到K)

Code:
#include<bits/stdc++.h>
using namespace std;
int N,K,ans,tmp;
int main()
{
    scanf("%d%d",&N,&K);
    for(int i=1;i<=N;i++){
        scanf("%d",&tmp);
        ans=max(ans,tmp);//max函数,在两个元素里取最大值
    }
    printf("%d",ans);
    return 0;
}

I 时间复杂度与TLE

前置芝士:string类型 double类型
题意:

给出时间限制t和复杂度数量级N以及程序的时间复杂度,判断当前程序的状态:超时/危险/安全

解法:

因为该题的数据范围有点大,对于 N 3 N^3 N3都超过了 l o n g long long l o n g long long的数据范围,那么考虑高精度

那么考虑用double类型来存储N,double类型的数据范围很大,大概在10300左右

那如何判断当前的时间复杂度字符串呢?

在C语言中可以使用char数组以及它的比较函数strcmp来完成

这里提供C++中更方便的string类型来完成

string为C++自带的字符串类型,

与其他数据类型(int,double等等)一样,它也具有大小比较的功能

那就是:"<"、">"、"=="

没有看错,不需要调用其他的函数,直接使用大于小于符号即可

string类型的比较与strcmp相同,两个字符从左向右逐个字符相比,直到出现不同的字符或遇‘\0’为止

那么这题的代码就简单了,直接使用“==”判断是否为对应的复杂度:

Code:
#include<bits/stdc++.h>
using namespace std;

int T;
double num;
string str;//定义一个string类型的变量

int main()
{
    scanf("%d%lf",&T,&num);
    cin>>str;//string类型只能通过cin>>来进行输入,cin>>为C++中独有的输入模式
    if(str=="n^2") num=pow(num,2);//pow为求幂的函数
    else if(str=="n^3") num=pow(num,3);
    else if(str=="nlogn") num=num*log2(num); //log2为底为2的求对数的函数
 
    if(num<=T*1e7) printf("Safe");
    else if(num<=T*1e8) printf("Dangerous");
    else printf("TLE");
    return 0;
}

E 希望得到的成绩

前置芝士:多组输入 排序函数 long long类型
题意:

多组输入,在给出的N门成绩中,选出K门最大的,并计算这K门成绩的平均值

解法:

不要被这个多组输入吓到了呀

就是你的代码要具有输入多组测试样例并输出的功能,具体看代码

对于排序而言,在C++的STL库中有排序的函数:sort()

具体用法为:sort( 排序数组的首地址, 排序数组的末地址+1, 排序的规则);

如果没有排序的规则,那么sort默认为升序排序

若要修改为降序排序,则排序的规则可以修改为:greater<int>()

在后续的学习中,会学习到更复杂的排序方式,如结构体排序等等

明显可见这个sort函数是一个前闭后开的结构,在后续的学习中会遇到很多这样的结构

该sort函数的时间复杂度为 O ( l o g 2 N ) O(log_2N) O(log2N),而普通的冒泡、选择排序 O ( N 2 ) O(N^2) O(N2),是无法通过本题1e5的数据范围的

举个栗子:

比如说要对数组中的第 0 号元素 A[0] 到第 4 号元素 A[4] 进行排序,那么则为:sort(A,A+5);
A就是数组的首地址,A+5就是数组的末地址+1
而自定义成降序排序,则为:sort(A,A+5,greater<int>());

在本题中,在完成了降序排序后,只需要对数组的前K个数求和然后求平均值即可

然而还有个坑点就是,在本题中,前K个数的值可能超过了int的范围,需要开long long

Code:
#include<bits/stdc++.h>
using namespace std;

const int MAXN=1e5+5;
int T,N,K;
int arr[MAXN];

int main()
{
    scanf("%d",&T);	//T组测试样例
    while(T--){	//先判断T是否为0,再将T减1,相当于循环T次
        scanf("%d%d",&N,&K);
        for(int i=1;i<=N;i++) scanf("%d",&arr[i]); //不需要对数组清零,因为输入时即覆盖了
        sort(arr+1,arr+1+N,greater<int>());	//对数组进行升序排序,由于是从1开始的,那么首末地址都需要加1
        long long sum=0;	//记录前K大的和
        for(int i=1;i<=K;i++) sum+=arr[i];
        printf("%.8lf\n",sum*1.0/K); //先乘1.0转换为double类型,由于需要在1e-6的误差内,保险起见输出小数点后8位
    }
    return 0;
}

G 回形取数求和

前置芝士: 标记数组 方向数组
题意:

给出一个N × \times ×M的矩阵,然后按下图所示的顺序来进行遍历:

img

然后对遍历的奇数项和偶数项分别求和输出。

解法:

这题最容易想到的方法就是按照题目的要求进行模拟,然后纪录

这里提供两个小技巧

首先是标记数组:

把走过的位置和不可走的位置(边界)标记为-1,而为0的地方就代表可以走的位置

方向数组:

定义两个大小为4的数组xx和yy,按照顺序分别存着右、下、左、上四个方向

然后按照这四个方向的顺序来遍历这个矩阵,

首先向右走,当遇到障碍时向下走,然后遇到障碍向左走,最后向上走,

而向上走遇到障碍的时候,方向就转变成了向右走,又回到了一开始向右走的状态

那这个不就是一个循环的结构嘛!?考虑用取余数来实现

具体实现见代码

Code:
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 2005;
int arr[MAXN][MAXN];//原数组的值
int brr[MAXN][MAXN];//标记数组
int N,M,cnt=1,op=0;//cnt为已经遍历的方格数,op为当前的方向,op在0~3之间
int xx[]={0,1,0,-1};//xx和yy分别为方向数组
int yy[]={1,0,-1,0};
int x=1,y=1;//纪录当前的位置
long long ans1,ans2;//纪录奇数或偶数的值,数据爆int了!开longlong

int main()
{
    memset(brr,-1,sizeof(brr));	//将brr数组按字节初始化为-1
    scanf("%d%d",&N,&M);
    for(int i=1;i<=N;i++)
        for(int j=1;j<=M;j++){
            scanf("%d",&arr[i][j]);
            brr[i][j]=0;  //brr为0时 则为当前位置可以走
        }
    
    while(cnt<=N*M){ //一共需要遍历N*M大小的矩阵
        if(cnt%2==1) ans1+=arr[x][y]; //cnt为奇数时将其加入ans1中
        else ans2+=arr[x][y];//当为偶数时加入ans2中
        brr[x][y]=-1;//将当前位置标记为-1,代表当前位置走过了,不可再走
        if(brr[x+xx[op]][y+yy[op]]==-1) op=(op+1)%4;//若行走的下一个位置不可走,则换方向,取余数即为让方向循环
        x+=xx[op]; y+=yy[op];	//在当前方向(op)前进一个单位
        cnt++;//同时将走过的位置数量加一
    }
  
    if(ans1==0) printf("-1\n");//若没有数纪录时,直接输出-1,下同
    else printf("%lld\n",ans1);
    if(ans2==0) printf("-1");
    else printf("%lld",ans2);
    return 0;
}

有更简单的方法:传送门


H Gcd and lcm

前置芝士:多组输入 结构体 递归 辗转相除法
题意:

给出a b c d,根据要求求出以下式子中的一个

在这里插入图片描述

解法:

熟悉的英文体面…

诶,这里又来了一个多组输入,这个多组输入跟前面的有啥区别呢?

woc,这个多组输入没有给有多少组!在文件读完时才算读入完毕,那这个咋整呢?

对于没有给定多少组输入时,有固定的输入模式:

while(scanf("%d",&N)!=EOF){
  //循环体
}

 //或者
while(~scanf("%d",&N)){
  //循环体
}

 //对于输入多个数据是一样的,在本题中应该如下所示
while(scanf(" %c %d/%d %d/%d",&ch,&A.zi,&A.mu,&B.zi,&B.mu)!=EOF){
  //循环体
}

而在%c前面留一个空格就会消除输入中的空格和回车的影响,(rzp:讲过了讲过了,下一个)

在本题解法中定义了一个结构体来表示一个分数,在这里就不赘述结构体的用法,当然不用结构体也可以做

这里提一个知识点,关于最大公因数GCD和最小公倍数LCM:

两个数的GCD和LCM的乘积与这两个数的乘积相等,表示为:

A × B = = G C D × L C M A\times B==GCD\times LCM A×B==GCD×LCM

而求最大公因数GCD时,用辗转相除法+递归+三目运算符的组合拳,就只有一行的代码

那用上面的结论,求最小公倍数LCM时,也只有一行的代码了…

当然也可以调用编译器自带的最大公因数函数:__gcd(a,b)

long long gcd(long long a,long long b)
{
  return b==0?a:gcd(b,a%b);
}

long long lcm(long long a,long long b)
{
  return a/gcd(a,b)*b;
}

由于这道题的数据不讲武德,如果a和b都为1e9时,求LCM时,a × \times ×b就会爆int,则需要开longlong

所以该题的代码如下:

Code
#include<bits/stdc++.h>
using namespace std;

struct fenshu{//分数结构体
    int zi,mu;
}A,B;
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}//强行压行
int lcm(int a,int b){return a/gcd(a,b)*b;}
char ch;

int main()
{
    while(scanf(" %c %d/%d %d/%d",&ch,&A.zi,&A.mu,&B.zi,&B.mu)!=EOF){
        int g1=gcd(A.zi,A.mu);
        int g2=gcd(B.zi,B.mu);
        A.zi/=g1; A.mu/=g1;
        B.zi/=g2; B.mu/=g2;
        fenshu ans;
        if(ch=='G'){
            ans.zi=gcd(A.zi,B.zi);
            ans.mu=lcm(A.mu,B.mu);
        }
        else{
            ans.zi = lcm(A.zi,B.zi);
            ans.mu = gcd(A.mu,B.mu);
        }
        int GCD = gcd(ans.zi,ans.mu);
        ans.zi/=GCD; ans.mu/=GCD;
        if(ans.mu==1) printf("%d\n",ans.zi);
        else printf("%d/%d\n",ans.zi,ans.mu);
    }
    return 0;
}

F 交际圈差距

前置芝士:并查集
题意:

有N个人,M个关系,问多个人连成的最大的圈子和最小的圈子的人数差值。

解法:

这题是并查集模版题,并查集在后续的学习中会进行讲解,在此处则不再赘述。

Code:
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e5+5;
int N,M,a,b;
int Fa[MAXN];
int ans[MAXN];
void uni(int x,int y);
int find(int x);

int main()
{
    scanf("%d%d",&N,&M);
    for(int i=1;i<=N;i++) Fa[i]=i;
    for(int i=1;i<=M;i++){
        scanf("%d%d",&a,&b);
        uni(a,b);
    }
    for(int i=1;i<=N;i++) ans[find(i)]++;
    int mn=1e9,mx=-1e9;
    for(int i=1;i<=N;i++){
        if(ans[i]){
            mx=max(mx,ans[i]);
            mn=min(mn,ans[i]);
        }
    }
    if(mx==N) printf("WOW");
    else printf("%d",mx-mn);
    return 0;
}

void uni(int x,int y)
{
    Fa[find(x)]=find(y);
}

int find(int x)
{
    if(x==Fa[x]) return x;
    else return Fa[x]=find(Fa[x]);
}

C Pokémon Go

前置芝士:广度优先搜索 BFS
题意:

在N × \times ×M ( 1 ≤ N , M ≤ 1000 ) (1 \leq N,M \leq 1000) (1N,M1000)的矩阵中,给出K( 0 ≤ K ≤ 1 0 4 0\leq K \leq 10^4 0K104)个障碍,T( 1 ≤ T ≤ 10 1\leq T\leq 10 1T10)个出口,

共有P( 1 ≤ P ≤ 1 0 5 1\leq P\leq 10^5 1P105)组询问,在(x, y)位置能否走到出口,若可以则输出走到某个出口的最短路径

解法:

该题为略微改版的 BFS ,在后续的学习中会学到,在这里不讲述其原理,

最朴素的方法就是对于每一组询问进行一次 BFS ,那么最坏情况的时间复杂度为: O ( N M P ) O(NMP) O(NMP),必然不能通过

观察到出口的个数远少于询问的次数,考虑对出口进行 BFS

然后对可以走到的地点进行标记,若多次搜索到,则取其中的最小值

当查询时,直接输出矩阵中纪录下的该元素的具体值即可,时间复杂度为: O ( N M T + P ) O(NMT+P) O(NMT+P),可以接受。

Code:
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 1e3+5;
const int INF = 1e9+7;
struct node{int x,y,val;};
int N,M,K,T,P;
int arr[MAXN][MAXN];
int vis[MAXN][MAXN];
int xx[]={1,0,-1,0};
int yy[]={0,1,0,-1};
int a,b;
void bfs(int x,int y)
{
    queue<node>Q;
    Q.push(node{x,y,0});
    arr[x][y]=0; vis[x][y]=1;
    while(!Q.empty()){
        node now = Q.front(); Q.pop();
        for(int i=0;i<4;i++){
            if(arr[now.x+xx[i]][now.y+yy[i]]!=-1&&!vis[now.x+xx[i]][now.y+yy[i]]){
                vis[now.x+xx[i]][now.y+yy[i]]=1;
                int tmp = arr[now.x+xx[i]][now.y+yy[i]];
                arr[now.x+xx[i]][now.y+yy[i]]=min(now.val+1,tmp);
                Q.push(node{now.x+xx[i],now.y+yy[i],min(now.val+1,tmp)});
            }
        }
    }
}
void clear(){
    for(int i=1;i<=N;i++)
        for(int j=1;j<=M;j++)
            vis[i][j]=0;
}

int main()
{
    memset(arr,-1,sizeof(arr));
    scanf("%d%d%d",&N,&M,&K);
    for(int i=1;i<=N;i++)
        for(int j=1;j<=M;j++)
            arr[i][j]=INF;
    for(int i=1;i<=K;i++){
        scanf("%d%d",&a,&b);
        arr[a][b]=-1;
    }
    scanf("%d",&T);
    for(int i=1;i<=T;i++){
        clear();
        scanf("%d%d",&a,&b);
        bfs(a,b);
    }
    scanf("%d",&P);
    for(int i=1;i<=P;i++){
        scanf("%d%d",&a,&b);
        if(arr[a][b]!=INF) printf("%d\n",arr[a][b]);
        else printf("-1\n");
    }
    return 0;
}

B 跳棋

前置芝士:深度优先搜索 DFS 记忆化
题意:

在下述的跳棋盘中进行操作:

img

在1和9号位置上的数字给定的情况下,将1~12数字分别不重复的放在棋盘上,

使所有的4个数字的线段的和相等,求出所有的解并输出

解法:

深度优先搜索 DFS 不在此处进行讲解,

第一想法是进行全排列,调用next_permutation()进行求解,但是时间复杂度最坏为 O ( 10 ! × 1 0 3 ) O(10!\times10^3) O(10!×103)

那么解法就一定是DFS+剪枝了,DFS很简单,直接对每个点进行试探即可,

观察可得这四个数字的和一定是26,那么显然有以下几种剪枝:

  • 将这四个数字相加求和,若大于26,则返回
  • 若一条线段中四个数字已经填上,若不等于26,则返回

写了这两条剪枝后,还是只能通过大概70%的数据

继续分析可得,固定的1号和9号,最多只有 12 × 11 = 132 12\times11=132 12×11=132种情况,而题目却会询问1000次

那么询问的时候一定会有重复的询问,那么在每次求解后,将求解后的答案纪录下来,

若下次再次询问与当前的值相同的情况时,直接输出记录下的答案即可。

Code:
#include<bits/stdc++.h>
using namespace std;

int a,b,flag;
int arr[15];
int ans[13][13][13][100];		//对答案进行记录,1维2维分别为a和b的值,三维为一条答案,四维为每条答案开辟的空间
int cnt[13][13];	//cnt[i][j]为记录当a=i,b=j时的答案个数
int mem[13][13]; //mem[i][j]用于记录当a=i,b=j时,是否已经求解过,并且记录是否有解
bool vis[15];

inline bool judge(int x){//两个剪枝的过程
    if(arr[1]+arr[3]+arr[6]+arr[8]>26) return true;
    if(arr[1]+arr[4]+arr[7]+arr[11]>26) return true;
    if(arr[8]+arr[9]+arr[10]+arr[11]>26) return true;
    if(arr[2]+arr[3]+arr[4]+arr[5]>26) return true;
    if(arr[2]+arr[6]+arr[9]+arr[12]>26) return true;
    if(arr[5]+arr[7]+arr[10]+arr[12]>26) return true;
    if(x==6) if(arr[2]+arr[3]+arr[4]+arr[5]!=26) return true;
    if(x==9) if(arr[1]+arr[3]+arr[6]+arr[8]!=26) return true;
    if(x==12){
        if(arr[1]+arr[4]+arr[7]+arr[11]!=26) return true;
        if(arr[8]+arr[9]+arr[10]+arr[11]!=26) return true;
    }
    if(x==13){
        if(arr[2]+arr[6]+arr[9]+arr[12]!=26) return true;
        if(arr[5]+arr[7]+arr[10]+arr[12]!=26) return true;
    }
    return false;
}

void dfs(int x)
{
    if(judge(x)) return;
    if(x==9) {dfs(x+1); return;};//第九个点已经固定
    if(x>=13){
        cnt[a][b]++;
        for(int i=1;i<=12;i++){
            printf("%d ",arr[i]);
            ans[a][b][i][cnt[a][b]]=arr[i];//记录下答案在第三维中
        }
        printf("\n");
        flag=1;
        return;
    }
    
    for(int i=1;i<=12;i++){
        if(!vis[i]){
            vis[i]=true;
            arr[x]=i;
            dfs(x+1);
            arr[x]=0;
            vis[i]=false;
        }
    }
    return;
}

int main()
{
    while(scanf("%d%d",&a,&b)!=EOF){//多组输入
        if(mem[a][b]==-1) {printf("-1\n\n"); continue;}
        if(mem[a][b]==1){//若为1或-1则已经求解过答案,直接输出即可
            for(int i=1;i<=cnt[a][b];i++){
                for(int j=1;j<=12;j++)
                    printf("%d ",ans[a][b][j][i]);
                printf("\n");
            }
            printf("\n");
            continue;
        }
        
        flag=0;
        vis[a]=vis[b]=true;
        arr[1]=a; arr[9]=b;
        dfs(2);//从第二个点进行搜索
        if(!flag) {printf("-1\n\n");mem[a][b]=-1;}
        else {printf("\n"); mem[a][b]=1;}
        vis[a]=vis[b]=false;
    }
    return 0;
}

打表过法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值