SWPU-ACM集训队周赛之个人赛(4-3,蓝桥杯模拟赛)----题解

时间:2021-12-08 13:50:45

今天的比赛题目是今年蓝桥杯的模拟赛原题,所以题解大概是可以在网上给搜索到的,但是希望同学们都是本着一个公平公正的态度来打这一场比赛的,这一场比赛大概前两题做出来,代码填空做对,编程大题大概一题半就可以拿到省一了,因为这次蓝桥杯官方的模拟赛,比赛连接在这里蓝桥杯模拟赛


A:题意杀,可能很多同学把18xx里面的x当成了题目描述中的x,实际上两者是没有关系的,给出这样一个东西实际上是给出你数字的范围是1800~1899,这样可以避免出现多种情况,题意明白了这道题目就很简单了,大概按照题意去算一下就可以了,答案是1806

#include <bits/stdc++.h>
 
using namespace std;
 
int main() {
    int i;
    for(int i=1;i<=99;i++){
        if(i*i-i >=1800&&i*i-i <= 1899){
            cout<<i*i-i<<endl;break;
        }
    }
    return 0;
}


B:典型的蓝桥杯暴力题,按照题意枚举就可以了,写一个cot数组保存每个数字出现的数字就可以了,答案算出来是40096

#include <bits/stdc++.h>
 
using namespace std;
 
int cot[10];
 
bool check(int a,int b){
    int a1 = a/100,a2 = (a%100)/10,a3 = a%10;
    int b1 = b/100,b2 = (b%100)/10,b3 = b%10;
    cot[a1]++;cot[a2]++;cot[a3]++;cot[b1]++;cot[b2]++;cot[b3]++;
    int x1 = a*b3,x2 = a*b2,x3 = a*b1;
    int sum = a*b;
    if(x1 < 100||x1 > 999||x2 < 100||x2 > 999||x3 < 100||x3 > 999||sum > 99999||sum < 10000)
        return false;
    cot[x1/100]++;cot[(x1%100)/10]++;cot[x1%10]++;
    cot[x2/100]++;cot[(x2%100)/10]++;cot[x2%10]++;
    cot[x3/100]++;cot[(x3%100)/10]++;cot[x3%10]++;
    cot[sum/10000]++;cot[(sum%10000)/1000]++;cot[(sum%1000)/100]++;
    cot[(sum%100)/10]++;cot[sum%10]++;
    for(int i = 0;i < 10;i++){
        if(cot[i]!=2) return false;
    }
    return true;
}
 
int main() {
    for(int i = 100;i <= 999;i++){
        for(int j = 100;j <= 999;j++){
            memset(cot,0,sizeof(cot));
            if(check(i,j)){
                cout<<i*j<<endl;
                return 0;
            }
        }
    }
    return 0;
 
}


C:这道题目应该是比较简单的(前提是你得知道康托展开,一个蓝桥杯经常考的算法),下面简单解释一下康托展开:

       康托展开其实就是一种特殊的哈希函数

  把一个整数X展开成如下形式:X=a[n]*n!+a[n-1]*(n-1)!+...+a[2]*2!+a[1]*1!

  其中a为整数,并且0<=a<i,i=1,2,..,n

  {1,2,3,4,...,n}表示1,2,3,...,n的排列,如 {1,2,3} 按从小到大排列一共6个。123 132 213 231 312 321 。代表的数字 1 2 3 4 5 6 也就是把10进制数与一个排列对应起来。他们间的对应关系可由康托展开来找到。如我想知道321是{1,2,3}中第几个大的数可以这样考虑 :第一位是3,当第一位的数小于3时,那排列数小于321 如 123、 213 ,小于3的数有1、2 。所以有2*2!个。再看小于第二位2的:小于2的数只有一个就是1 ,所以有1*1!=1 所以小于321的{1,2,3}排列数有2*2!+1*1!=5个 。所以321是第6个大的数。 2*2!+1*1!是康托展开。再举个例子:1324是{1,2,3,4}排列数中第几个大的数:第一位是1小于1的数没有,是0个 0*3! 第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2,1*2! 。第三位是2,小于2的数是1,但1在第一位,所以有0个数 0*1! ,所以比1324小的排列有0*3!+1*2!+0*1!=2个,所以1324是第三个大数,不知道大家有没有看明白这个康托展开的解释,那么接下来我们要怎样去用代码实现这样的一个康托展开呢?当然,康拓展开的时间复杂度是O(n^2)的,因为是蓝桥杯,所以,放心大胆的去艹吧

//参数int s[]为待展开之数的各位数字,如需展开2134,则s[4]={2,1,3,4}.
int fac[]= {1,1,2,6,24,120,720,5040,40320,362880}; //...
long cantor(int s[],int n)
{
    int i,j,temp,num;
    num=0;
    for(i=0; i<n; i++){
        temp=0;
        for(int j=i+1; j<n; j++){
          if(s[j]<s[i]) temp++;  
        }
    num+=fac[n-i]*temp;  
    }
    return (num+1);  
}
那么当我们知道了康拓展开之后我们就能很轻松的解决这道题了,由于这道题目中的排序是字符,所以我们将其认为是ASCLL码就可以了,另外,由于数字特别大,需要处理为long long
#include <bits/stdc++.h>
 
using namespace std;
const int N = 17;
 
int main()
{
    char tmp[] = "bckfqlajhemgiodnp";
    long long x = 1;
    long long sum = 0;
    int cnt = 0;
    for(int i = 1; i < N; i++)
    {
        x *= i;//用于求阶乘
        cnt = 0;//算出后面的序列有几个比当前tmp[i]
        for(int j = N-i; j < N; j++)
        {
            if(tmp[N-i-1]>tmp[j])
            {
                cnt++;
            }
        }
        sum += cnt*x;//求和
    }
    cout << sum;
    return 0;
}

 

D、E:蓝桥杯典型的代码填空题,这种题目实际上你把他的代码拿出来,然后在填空处放上你的代码,自己跑一下就知道到底自己做没做对了,因为评测环境不同,所以这道题可能一些同学的答案是对的,所以给出答案,大家自己去对照一下:答案是t>0?t+1:t-1,实际上就是去算答案到底是正值还是负值,因为t的值就代表了两个串之间的大小关系,++ --其实就是简单的去算位置而已,当然答案会有很多种写法,所以,不一定这就是标准答案



F:蓝桥杯的第一道编程大题一定是一道暴力的傻逼题,所以,这道题我们只需要枚举然后不断算取最接近的值,当遇到绝对值反超时,就break,具体解法看代码:

public class Main {
    public static void main(String[] args) {
        Scanner sn = new Scanner(System.in);
        double m = sn.nextDouble()/100/12;
        int month = sn.nextInt();
        int money = 10000*100;//用分来表示
        int min = money/month;
        int ans = 0x7fffffff,last = 0x7fffffff;//分别表示结果和绝对值最小的那个值
        for(int i = min;;i++){
            int moneys = money;
            for(int j = 1;j<=month;j++){//从最小还款可能枚举
                moneys = get(moneys*(1+m)-i);
            }
            if(Math.abs(last)>Math.abs(moneys)){
                last = moneys;
                ans = i;
            }else break;//如果绝对值反超,直接break
        }
        System.out.println(ans);
        sn.close();
    }

    private static int get(double money) {

        return (int)(money+0.5);//因为已经表示为分,所以加上小数点后一位就能判断是否需要进位。
    }
}

#include <bits/stdc++.h>

using namespace std;

int get(double money){
    return (int)(money+0.5);//因为已经表示为分,所以加上小数点后一位就能判断是否需要进位。
}
int main(){
    double m;
    int month;
    scanf("%lf %d",&m,&month);
    m /= 1200;
    int money = 10000*100;//用分来表示
    int min = money/month;
    int ans = 0x7fffffff,last = 0x7fffffff;//分别表示结果和绝对值最小的那个值
    for(int i = min;;i++){
        int moneys = money;
        for(int j = 1;j<=month;j++){//从最小还款可能枚举
            moneys = get(moneys*(1+m)-i);
        }
        if(abs(last)>abs(moneys)){
            last = moneys;
            ans = i;
        }else break;//如果绝对值反超,直接break
    }
    printf("%d",ans);
    return 0;
}


G:这道题目实际上是hihocoder上的一道原题,也是google当年网招的一道签到题(所以蓝桥杯的组委会经常会搞出原题这种事情),这道题其实我们只需要考虑一些特殊情况,而且因为数字只有10个数,所以我们可以通过dfs来完成这道题目

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Scanner;


public class Main {
    static boolean[][] a = new boolean[10][10];
    static boolean[] b = new boolean[10];
    static int sum;
    static int n;
    static int[][] c;
    static int[] d = new int[11];
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        a[1][3] = true;
        a[1][7] = true;
        a[1][9] = true;
        a[2][8] = true;
        a[3][9] = true;
        a[3][7] = true;
        a[4][6] = true;
        a[7][9] = true;
        n = input.nextInt();
        c = new int[n][2];
        for(int i=0;i<n;i++){
            c[i][0] = input.nextInt();
            c[i][1] = input.nextInt();
        }
        f(1,1);
        System.out.println(sum);
    }
    public static boolean  check(int i){
        int j,k;
        for(j=0;j<n;j++){
            for(k=1;k<=i;k++){
                if(d[k]==c[j][0]||d[k]==c[j][1]){
                    if(d[k]==c[j][0]){
                        if(d[k-1]==c[j][1])    break;
                    }else{
                        if(d[k-1]==c[j][0])    break;
                    }
                }
            }
            if(k>i)    return false;
        }
        return true;
    }
    public static void f(int i,int h){
        if(i>4){
            if(check(i-1))
            sum++;
        }
        if(i>9) return;
        
        for(int j=1;j<=9;j++){
            if(b[j]!=true){
                if(i>1){
                    if(a[h][j]!=true&&a[j][h]!=true){
                        b[j] = true;
                        d[i] = j;
                        f(i+1,j);
                        b[j] = false;
                    }else if(b[(h+j)/2]){
                        d[i] = j;
                        b[j] = true;
                        f(i+1,j);
                        b[j] = false;
                    }
                }else{
                    d[i] = j;
                    b[j] = true;
                    f(i+1,j);
                    b[j] = false;
                }
            }
            
        }
    }
}

#include <bits/stdc++.h>

using namespace std;

int filter[10][10];
int stamp[9];
bool vis[10];
bool know[10][10];
int result,n;
void dfs(int cot)
{
    if(cot>=4)
    {
        int nKnow=0;
        for(int i=1;i<cot;i++)
        {
            if(know[stamp[i]][stamp[i-1]])
            nKnow++;
        }
        if(n==nKnow)
        result++;
    }
    for(int i=1;i<=9;i++)
    {
        if(cot>0&&!vis[filter[stamp[cot-1]][i]])
        continue;
        if(!vis[i])
        {
            vis[i]=1;
            stamp[cot]=i;
            dfs(cot+1);
            vis[i]=0;
        }
    }
    return ;
}
int main()
{
    memset(filter,0,sizeof(filter));
    filter[1][3]=filter[3][1]=2;
    filter[4][6]=filter[6][4]=5;
    filter[7][9]=filter[9][7]=8;
    filter[1][7]=filter[7][1]=4;
    filter[2][8]=filter[8][2]=5;
    filter[3][9]=filter[9][3]=6;
    filter[1][9]=filter[9][1]=5;
    filter[3][7]=filter[7][3]=5;
    vis[0]=true;
    int ncase;
        result=0;
        scanf("%d",&n);
        memset(know,false,sizeof(know));
        for(int i=0;i<n;i++)
        {
            int a,b;
            scanf("%d %d",&a,&b);
            know[a][b]=know[b][a]=true;
        }
        dfs(0);
        printf("%d\n",result);
    return 0;
}



H:这道题目的意思实际上是给你一个无权图,然后让我们找核心站,那么这种题目一般是有并查集和搜索两种写法的,这里给出并查集思路,我们需要先用并查集检验【a,b】是否能联通,不能联通直接输出-1,结束程序。能联通,那么考虑除了a、b两站的其他站,其他站依次删除然后并查集寻找【a,b】的father,如果father相同,那么结果+1。所以枚举所有点即可(除了a,b两点),然后用并查集设置father,在最后再用并查集寻找k1,k2两点的father,如果相同,那么说明肯定联通。

public class Main {
    private static int[] father = new int[1001];
    private static int n,m;
    private static int[][] ar;
    private static int t1,t2;
    public static void main(String[] args) {
        Scanner sn = new Scanner(System.in);
        n = sn.nextInt();
        m = sn.nextInt();
        ar = new int[1001][2];
        for(int i = 0;i<m;i++){//表示第i个存储的两站连接点
            ar[i][0] = sn.nextInt();
            ar[i][1] = sn.nextInt();
        }
        t1 = sn.nextInt();
        t2 = sn.nextInt();
        int count = 0;
        if(!isLinked()){System.out.println(-1);return;}//判断初试两点是否联通,不能联通直接结束程序
        for(int i = 1;i<=n;i++){
            if(i==t1||i==t2)continue;//去除需要检查联通的两点,这两点不需要遍历
            for(int j = 1;j<=n;j++)father[j] = j;//初始化
            for(int j = 0;j<m;j++){
                    if(ar[j][0]==i||ar[j][1]==i)continue;//去除的这个点所在的线段就不存在了
                    int a = findF(ar[j][0]);
                    int b = findF(ar[j][1]);
                    if(a>b){a^=b;b^=a;a^=b;}
                    if(a!=b)father[b] = a;  //以小的为父节点
            }
            int a = findF(t1);
            int b = findF(t2);
            if(a!=b)count++;//如果共同父亲不同,那么肯定不连通,即那条被剪去的线路是核心线路,在这线路上有一头是核心站

        }
        System.out.println(count);
        sn.close();
    }
    public static boolean isLinked(){
        for(int j = 1;j<=n;j++)father[j] = j;
        for(int j = 0;j<m;j++){
                int a = findF(ar[j][0]);
                int b = findF(ar[j][1]);
                if(a>b){a^=b;b^=a;a^=b;}
                if(a!=b)father[b] = a;  //以小的为父节点
        }
        int a = findF(t1);
        int b = findF(t2);
        if(a==b)return true;
        return false;
    }
    private static int findF(int i) {
        if(father[i]==i)return i;
        return father[i] = findF(father[i]);
    }
}

#include <bits/stdc++.h>

using namespace std;

int father[1001],n,m,ar[1001][2],t1,t2;
int findF(int i) {
    if(father[i]==i)return i;
    return father[i] = findF(father[i]);
}
int isLinked(){
    for(int j = 1;j<=n;j++)father[j] = j;
    for(int j = 0;j<m;j++){
            int a = findF(ar[j][0]);
            int b = findF(ar[j][1]);
            if(a>b){a^=b;b^=a;a^=b;}
            if(a!=b)father[b] = a;  //以小的为父节点
    }
    int a = findF(t1);
    int b = findF(t2);
    if(a==b)return 1;
    return 0;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i = 0;i<m;i++){//表示第i个存储的两站连接点
        scanf("%d%d",*(ar+i),*(ar+i)+1);
    }
    scanf("%d%d",&t1,&t2);
    int count = 0;
    if(!isLinked()){printf("-1");return 0;}//判断初试两点是否联通,不能联通直接结束程序
    for(int i = 1;i<=n;i++){
        if(i==t1||i==t2)continue;//去除需要检查联通的两点,这两点不需要遍历
        for(int j = 1;j<=n;j++)father[j] = j;//初始化
        for(int j = 0;j<m;j++){
                if(ar[j][0]==i||ar[j][1]==i)continue;//去除的这个点所在的线段就不存在了
                int a = findF(ar[j][0]);
                int b = findF(ar[j][1]);
                if(a>b){a^=b;b^=a;a^=b;}
                if(a!=b)father[b] = a;  //以小的为父节点
        }
        int a = findF(t1);
        int b = findF(t2);
        if(a!=b)count++;//如果共同父亲不同,那么肯定不连通,即那条被剪去的线路是核心线路,在这线路上有一头是核心站

    }
    printf("%d",count);
    return 0;
}
题解赶得比较匆忙,有很多细节的东西没有考虑到,也有一些代码没有手验,所以这份博客可能还会有所更新,另外,如果想要学习并查集以及搜索入门的同学可以看一下这两份视频:搜索入门 并查集 最后祝大家在蓝桥杯省赛中轻松省一,北京旅游!!!