2020-2021年度第二届全国大学生算法设计与编程挑战赛(冬季赛)——正式赛(Java语言题目解析)

附上这些题目的链接

这是热身赛

排列巨人

题目描述:

有12个数字,分别是1~12。
计算并输出这12个数字的全排列的种类数。

解题思路:
由题意知:题目实际要求1~12全排列的种类数。
由简单的数学知识我们知道:1~12的全排列的种类数为 12 !

package content;/**
 * Copyright (C), 2019-2021
 * author  candy_chen
 * date   2021/3/1 17:10
 *
 * @Classname test
 * Description: 算法竞赛:排列巨人
 */

/**
 *
 */
public class Tester_01{
    public static void main(String[] args){
        int n = 12;
        int count = n * f(n - 1);
        System.out.print("对于12名巨人的排列方式有" + count +"种:" );

        int[] buf = {1,2,3,4,5,6,7,8,9,10,11,12};
        prem(buf,0,buf.length - 1);


        return;
    }

    private static void prem(int[] buf, int start, int end) {
        int count = 12;
        if(start==end){//输出排列好的数组
            System.out.print("{");
            for(int c:buf){
                System.out.print(c);
                if (count > 1){
                    System.out.print(",");
                    count--;
                }

            }
            System.out.print("}");
            System.out.print("、");
        }else{
            for(int i=start;i<=end;i++){
                int temp=buf[start];//前后元素交换
                buf[start]=buf[i];
                buf[i]=temp;

                prem(buf,start+1,end);//递归交换后面的元素

                temp=buf[start];
                buf[start]=buf[i];
                buf[i]=temp;

            }
        }
    }

    private static int f(int i) {

        int count = 1;
        if (i > 0){
            count  = i *  f(i - 1) ;
        }
        return count;
    }

}

三子棋

在这里插入图片描述
解题思路:

数据范围很小,可以O(S*T)枚举每一个位置的棋子。
接着判断这个棋子是否是横着、竖着、斜着(左上到右下、右上到左下两个方向)能够构成三连子。
如果可以,那么这个格子的棋子就是答案,直接输出即可。
全部棋子枚举完没有找到三连子,意味着平局,输出ADPC! 注意叹号是英文叹号

mp[i][j]为第i行第j列的格子上的棋子或无人下棋
	第i行为:从上到下的第i行
	第j列为:从左到右的第j列
在枚举过程中,设当前棋子为mp[i][j]
则左边挨着的棋子所在的列即为mp[i][j-1]、右边为mp[i][j+1]
判断三者是否相等即可完成横着方向的判断。
之后在竖、斜方向的判断仍然同理。
import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Closeable;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.InputStream;
import java.util.Scanner;

//判断是否连成三子棋,可以枚举棋盘上每一个位置的棋子。
//接着判断这个棋子是否是横着、竖着、斜着(左上到右下、右上到左下两个方向)能够构成三连子,
//如果可以,那么这个格子的棋子就是答案,直接输出即可。
//全部棋子枚举完没有找到三连子,意味着平局,输出ADPC!    注意叹号是英文叹号
//

public class Main {
    public static void main(String [] args) {
        Scanner in  = new Scanner(System.in);
        int s = in.nextInt();
        int t = in.nextInt();// 输入棋盘的行和列
        char mp[][] = new char[33][33];
        //mp[i][j]为第i行第j列的格子上的棋子或无人下棋
        //第i行为:从上到下的第i行
        //第j列为:从左到右的第j列
        String str;
        for(int i = 1; i <= s; i++) {
            str = in.next();
            for(int j = 1; j <= t; j++) {
                mp[i][j] = str.charAt(j - 1);
            }
        }

        for(int i = 1; i <= s; i++) {
            for(int j = 1; j <= t; j++) {
                //判断当前棋子为中心的横着三个棋子是否构成三子棋,即当前棋子和它左右两棋子是否相同
                if(mp[i][j-1]==mp[i][j]&&mp[i][j]==mp[i]j+1]&&mp[i][j]!='.'){
                    System.out.println(mp[i][j]);//此时连成三子棋,输出此位置字符即为答案
                    return ;//return 0执行,程序结束。
                }
                //判断当前棋子为中心的竖着三个棋子是否构成三子棋,即当前棋子和它上下两棋子是否相同
                if(mp[i-1][j]==mp[i][j]&&mp[i][j]==mp[i+1][j]&&mp[i][j]!='.'){
                    System.out.println(mp[i][j]);//此时连成三子棋,输出此位置字符即为答案
                    return ;//return 0执行,程序结束。
                }
                //判断当前棋子为中心的斜着(左上到右下)三个棋子是否构成三子棋,即当前棋子和它左上、右下两棋子是否相同
                if(mp[i-1][j-1]==mp[i][j]&&mp[i][j]==mp[i+1][j+1]&&mp[i][j]!='.'){
                    System.out.println(mp[i][j]);//此时连成三子棋,输出此位置字符即为答案
                    return ;//return 0执行,程序结束。
                }
                //判断当前棋子为中心的斜着(右上到左下)三个棋子是否构成三子棋,即当前棋子和它右上、左下两棋子是否相同
                if(mp[i-1][j+1]==mp[i][j]&&mp[i][j]==mp[i+1][j-1]&&mp[i][j]!='.'){
                    System.out.println(mp[i][j]);//此时连成三子棋,输出此位置字符即为答案
                    return ;//return 0执行,程序结束。
                }
            }
        }
        System.out.println("ADPC!");//如果有胜者前面直接输出并return 0了,因此这里直接输出即可
        return ;
    }
}

钻石

题目描述:
N个物品,第i个物品体积为𝐴𝑖,价格为𝑆𝑖。
M个背包,每个背包只能装一个物品,第i个背包的容积为𝑅𝑖
问如何购买物品使得背包中的价格总和最大。

解题思路:

一眼看过去,似乎与背包问题有关。
但题意清楚之后,是一个很显然的贪心,因为每个背包最多只能装一个物品。

且如果一个小体积的背包x能够装下一个物品i,那么其他比此背包大的背包也能装下物品i
,但因为其他背包体积更大,所以可以有更多的选择:因为可能存在某些体积恰好大于x的
物品j,且其价值更大。

所以贪心思路很容易得到:
从最小的背包依次枚举,对于当前背包x:在能放入背包的物品中选择价格最大的放入其中
,并计入答案即可。

and:

首先,将背包升序排序,将物品按照体积升序排序。
我们可以创建一个优先队列(大根堆),里面存放物品的价格。
每枚举到一个背包,就加入一些新的物品,这些物品是能够装下当前背包,且不能装下之
前枚举的较小体积背包的。

题解

在这里插入代码片


这是正式赛

A - 塔

在这里插入图片描述

import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Closeable;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.InputStream;
import java.util.Scanner;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		/***********************************
		观察题目样例给出的高为5层的塔,可以得出以下几个规律
		对于一个高为n层的塔而言,首先设最上面一层(顶层)为第一层。
		1. 对于第i层而言,其字符的排列规律为:大写字母表中从第1个字符(A)~第i个字符,后又倒序从第i-1个字符~第1个字符(A)。
		2. 第1~n-1层每层前都有空格,具体而言,对于第i行,字符前面的空格个数为n-i个。
		找出以上规律后,我们就可以根据这些规律构造出答案:层高26的塔。

		TIPS:
		大写字母'A'为大写字母表第一个字符
		对于大写字母表中第i个字符,可以使用'A'+i-1得到。
		例如:第5个字符为'E',亦即为:'A'+5-1
		***********************************/
		
        char c1;
        int n=26;//设定塔的层数为26
        for(int i=1;i<=n;i++){ //对塔每一层按照规律进行构造。
            //首先进行输出空格的操作:对于第i行,字符前面的空格个数为n-i个。
            for(int j = 1;j<=n-i;j++) {
                System.out.print(" ");
            }
            for(int j=1;j<=i;j++){//按照规律1,输出第1~第i个大写字母。
                c1=(char) (j+'A'-1);//第j个大写字母为'A'+j-1
                System.out.print(c1);//输出第j个大写字母
            }
            for(int j=i-1;j>=1;j--){//按照规律1,输出第i-1~第1个大写字母,注意是倒序
                c1=(char) (j+'A'-1);
                System.out.print(c1);
            }
            System.out.println();//第i行输出结束,进行换行。
        }
    }

}

B - 日记

在这里插入图片描述
解题思路:

按照加密思路进行解密即可
遇到l、i、n、k、e时,其后面必是跟着bt和这个字母本身。
所以可以读取字符串后,for循环按顺序枚举并输出每个字母,但遇到l、i、n、k、e时不要
输出其后面的三个字母即可。

另一种做法:
使用getchar()按字母读取字符串,每读取到一个字母时就将其输出。
但遇到l、i、n、k、e时,使用三次getchar()读取后面加密多出来的字符,且不
输出即可跳过。

public class test_02 {
    public static void main(String[] args) {

        String s = "ibti lbtlovebte lbtlibtinbtnkbtkebtezbas jebte dosadnbtna ovakbtkebtemibtijaxaszxdbtddbtddbtddbtddbtddbtd";
        int n = s.length();
        for(int i = 0; i < n; i++) {
            System.out.print(s.charAt(i));
            if(s.charAt(i)=='l' || s.charAt(i)=='i' || s.charAt(i)=='n' || s.charAt(i)=='k' || s.charAt(i)=='e') i += 3;
        }

    }
}

D - 质数区间

在这里插入图片描述

package org.example;/**
 * Copyright (C), 2019-2021
 * author  candy_chen
 * date   2021/3/13 14:52
 *
 * @Classname Tester_02
 * Description: 测试
 */

import java.util.*;

/**
 *
 */
public class Main {

    public static List<List<Integer>> res = new ArrayList<>();

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc = new Scanner(System.in);
        int l = sc.nextInt();
        int r = sc.nextInt();
        int[] nums = new int[r];

        // 定义全局变量保存结果
        StringBuffer sb = new StringBuffer();

        //1既不是质数也不是和数,使用i从2开始。
        for (int i = 2; i <= r; i++) {
            boolean flag = true;
            for (int j = 2; j < i; j++) {
                if (i % j == 0) {
                    flag = false;
                    break;//到了1000内的质数,就跳出循环,并输出结果。
                }
            }
            if (flag) {
                nums[i] = i;
                int k = nums.length;
                res.add(Collections.singletonList(nums[i]));

                //sb.append(combine(nums[i],k));
				//还有bug。。。。。。..。。



                System.out.println(sb.append(res.toString()).toString());

            }


        }

    }

    //全排列问题

    public static List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res = new ArrayList<>();
        if (k <= 0 || n < k) {
            return res;
        }
        // 从 1 开始是题目的设定
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, k, 1, path, res);
        return res;
    }

    private static void dfs(int n, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
        // 递归终止条件是:path 的长度等于 k
        if (path.size() == k) {
            res.add(new ArrayList<>(path));
            return;
        }

        // 遍历可能的搜索起点
        for (int i = begin; i <= n; i++) {
            // 向路径变量里添加一个数
            path.addLast(i);
            // 下一轮搜索,设置的搜索起点要加 1,因为组合数理不允许出现重复的元素
            dfs(n, k, i + 1, path, res);
            // 重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
            path.removeLast();
        }


    }
}

E - 神仙爱采药

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

package content;/**
 * Copyright (C), 2019-2021
 * author  candy_chen
 * date   2021/3/14 21:11
 *
 * @Classname test_10
 * Description: 测试
 */

import java.util.Scanner;

/**
 *
 */
public class test_05 {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        String V = sc.nextLine();
        int k = Integer.parseInt(V);
        String s = sc.nextLine();
        long ans = 0, cnt1 = 0, cnt2 = 0;


        long n = s.length();
        for (int i = 0; i < n; i++)
        {
            if(s.charAt(i) == '2') {
                if(cnt1 + 2 * cnt2 + 2 <= k) cnt2++;
            } else {//chars[i]=='1'
                if(cnt1 + 2 * cnt2 + 1 <= k) cnt1++;
                else if(cnt2 > 0) {
                    cnt1++;
                    cnt2--;
                }
            }
            ans += cnt1 + cnt2; //计入答案
        }
        System.out.println(ans);



    }
}

F - 但更爱字符串

在这里插入图片描述

package content;/**
 * Copyright (C), 2019-2021
 * author  candy_chen
 * date   2021/3/14 20:39
 *
 * @Classname test_01
 * Description: 测试
 */

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 *
 */
public class test_01 {


    public static void main(String[] args) {
        String res = "";
        List<String> word = new ArrayList<>();
        String s = null;
        Scanner sc = new Scanner(System.in);
        while (sc.nextLine() != null){
            if (s.length() == 0){
                break;如果输入为空就停止
            }
            StringBuilder w = new StringBuilder();
            for (int i=0;i<s.length();i++){//循环s
                if (bigletter(s.charAt(i))||smallletter(s.charAt(i)))
                    w.append(s.charAt(i));//如果是字母则直接压到s里
                if (!bigletter(s.charAt(i))&&!smallletter(s.charAt(i))){//否则把单词压到word了,并把符号也压进去
                    word.add(w);
                    w = new StringBuilder();
                    w.append(s.charAt(i));
                    word.add(w);
                    w = new StringBuilder();
                }
            }

            word.add(w);//把最后一个单词压进去
            work(word);
            word.clear();//清空word

        }
    }
    public static boolean bigletter(char c){//判断是不是大写字母
        if(c>='A'&&c<='Z') return true;
        return false;
    }
    public static boolean smallletter(char c){//判断是不是小写字母
        if (c>='a'&&c<='z') return true;
        return false;
    }
    public static boolean bigletterword(String s){//判断是不是word
        if (!bigletter(s.charAt(0))) return false;
        if (s.length()<=1) return false;
        for (int i=1;i<s.length();i++){
            if (!smallletter(s.charAt(i))) return false;
        }
        return true;
    }
    public static void work(List<StringBuilder> word){//处理每一个单词或符号
        int n=word.size();
        for (int i=0;i<n;i++){
            int inow=i;
            if (!bigletterword(String.valueOf(word.toString().length())))//如果不需要缩写则直接输出
                System.out.print(word[i]);
            if (bigletterword(word[i])) {
                if (i!=n-1){//最后一位要么跟着前面的缩写,要么就是只有一个符合的所以不用缩写
                    if (word[i+1]==" "&&bigletterword(word[i+2])){
                        int j;
                        for (j=i;j<n;j++){
                            if ((j-i)%2==0){
                                if (!bigletterword(word[j])){
                                    j--;
                                    break;
                                }

                                else
                                    System.out.print(word[j][0]);
                            }
                            else if (word[j]!=" ") break;

                        }//j停在了最后符合要求的word的最后一位的后面一位
                        System.out.print(" (");
                        for (int k=i;k<j-1;k+=2)
                            System.out.print(word[k] + ' ');
                        System.out.print(word[j-1]+')');

                        inow=j-1;//i直接跳过缩写的部分
                    }
                    else
                        System.out.print(word[i]);
                }
                else
                    System.out.print(word[i]);
            }
            i=inow;
        }
    }


}

I - 奇怪的传输机增加了

在这里插入图片描述
在这里插入图片描述
解题思路:
for循环模拟即可
使用for循环从第1天枚举到第y天。
使用一个临时变量tmp去记录到第i天时,传输机剩下的数据量。
如果到第x天时,tmp += N/2,注意此处的N是一开始的N,即最初的数据量。
表示恢复了一半的数据。
在枚举过程中,每次都要判断当前剩余的数据量是否小于N/32。如果小于,则传
输失败,枚举结束后,如果没有任何传输失败,则还要输出传输了多少数据量。

public class test_09 {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        double n = sc.nextInt();
        int x = sc.nextInt();
        int y = sc.nextInt();

        double nn = 0, st = 1;
        for (int i = 1; i <= 5; ++i)
            st *= 0.5;
        for (int i = 1; i <= y; ++i) {
            n *= 2.0 / 3;
            if (i == x)
                n += nn / 2;
            if (n < st * nn) {
                System.out.println("N0!" );
                System.out.println(i +" " + n);

            }
        }
        System.out.println("YE5!");
        System.out.println(n);

    }
}

J - 奇怪的小鸭子也增加了

在这里插入图片描述题意简述:
有一个𝐴𝐴 ∗ 𝐵𝐵的大矩形,同时有若干个a ∗ 𝑏𝑏的小矩形。小矩形不能旋转。
问最少需要在大矩形中放入多少个小矩形,便再也无法放入更多的矩形。

解题思路:
一道很显然的数学计算题目。设水平方向长为A,竖直方向为B,(小矩形为水平
长a,竖直方向长b)
要使用最少的小矩形让这个大矩形中不能再放入更多的小矩形,显然:要把小矩
形在大矩形中排布的尽可能分散。

即水平方向上的两个小长方形的距离必须小于 a,竖直方向上的两个小矩形的距
离必须小于 b,否则就放得下其他的小长方形了。

在这里插入图片描述

public class test_10 {
    public static void main(String[] args) {

        Scanner sc = new Scanner(System.in);
        int A = sc.nextInt();
        int B = sc.nextInt();
        int a = sc.nextInt();
        int b = sc.nextInt();
        int K = (int) Math.ceil((A - a + 0.000001) / (2 * a - 0.000001));
        int C = (int) Math.ceil((B - b + 0.000001) / (2 * b - 0.000001));
        System.out.println(K * C);

    }
}

K - 关于哥俩好这事

在这里插入图片描述
解题思路:
第一眼可能觉得此题有点费解,但是数据范围只有5000。

同时要求找最小的和,所以我们不妨使用for循环逐个枚举,令i从最小值1枚举至无穷大。
对于每一个数字i,计算其数位和,设为f(i)。

则可以知道每个数字的数位和都不会太大,可以先开一个数组cnt[x]记录:到目前为止数位
和为x的数字个数。

同时设sum[x]记录:到目前为止数位和为x的数字之和。因为是从小到大顺序枚举的,所以
一定是最小和

本题通过简单枚举和数组即可解决:

在枚举的过程中更新cnt[x]数组和sum[x]数组
在某个cnt[x]的值刚好到n时,用当前的sum[x]去更新答案。
循环结束输出答案即可。

package content;/**
 * Copyright (C), 2019-2021
 * author  candy_chen
 * date   2021/3/14 21:11
 *
 * @Classname test_10
 * Description: 测试
 */

import java.util.Scanner;

/**
 *
 */
public class test_11 {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] cnt = new int[500];
        int[] sum = new int[500];
        int ans = Integer.MAX_VALUE;
        for (int i = 1; i <= 600000; i++) {
            int x = f(i);

            if(cnt[x] < n) {
                cnt[x]++;
                sum[x] += i;
            }
            if (cnt[x] == n) {
                ans = Math.min(ans, sum[x]);
            }


        }
        System.out.println(ans);


    }
    public static int f(int x) {
        int tot = 0;
        while (x != 0) {
            tot += x % 10;
            x /= 10;
        }
        return tot;
    }
}

L - 我们未知的那窝蛋的名字(难)

在这里插入图片描述
在这里插入图片描述

c++代码

#include<bits/stdc++.h> 
#define ll long long 
#define re register
using namespace std;
const int N=100005;

int n,m,len,opt;
int pos[N],L[N],R[N];
struct line
{
	ll k,b;//斜率和截距 
	bool operator<(line l)const
	{
		return k==l.k?b>l.b:k<l.k; //按斜率排序,斜率相同按截距 
	}
}a[N],b[N];
bool vis[N],use[N],need[N];
int head[N],tail[N]; //第i块在栈中的首尾 
int sta[N]; //栈,存直线的下标 
double point[N]; //记录交点 
inline ll max(ll x,ll y){return x>y?x:y;}

inline int read()
{
	int x=0,f=1;char st=getchar();
	while(st<'0'||st>'9'){if(st=='-') f=-1;st=getchar();}
	while(st>='0'&&st<='9') x=x*10+st-'0',st=getchar();
	return x*f;
}

inline void write(ll x)
{
	if(x<0) x=~x+1,putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}

inline double getx(line x,line y)
{
	return (double)(x.b-y.b)/(y.k-x.k);  //算两条直线交点的横坐标  
}

inline void rebuild(int x)
{
	int st=L[x]-1,top=L[x];
	for(re int i=L[x];i<=R[x];++i)
		if(vis[i]) b[++st]=a[i]; //把需要重构的直线加入 
	sort(b+L[x],b+st+1);
	sta[top]=L[x];
	for(re int i=L[x]+1;i<=st;++i)
	if(b[i].k!=b[i-1].k)  //如果两条直线斜率相同,只可能取到截距大的一条  
	{
		while(top>L[x]&&getx(b[i],b[sta[top]])<getx(b[sta[top]],b[sta[top-1]])) top--;
		point[top]=getx(b[i],b[sta[top]]); //算交点 
		sta[++top]=i;
	}
	head[x]=L[x];
	tail[x]=top;
}


inline void doit(int t,int l,int r)
{
	if(l>r) swap(l,r);
	ll tmp=-1e18;
	int p=pos[l],q=pos[r];
	if(p==q)
	{
		for(re int i=l;i<=r;++i)
			if(vis[i]) tmp=max(tmp,t*a[i].k+a[i].b); //暴力 
	}
	else 
	{
		for(re int i=l;i<=R[p];++i)
			if(vis[i]) tmp=max(tmp,t*a[i].k+a[i].b); 
		for(re int i=L[q];i<=r;++i)
			if(vis[i]) tmp=max(tmp,t*a[i].k+a[i].b);	
		for(re int i=p+1;i<q;++i)
		{
			if(need[i])  //如果需要重构 
			{
				rebuild(i);
				need[i]=0;
			}
			if(use[i])
			{
				while(head[i]<tail[i]&&point[head[i]]<t) head[i]++;  //找第一个大于等于t的交点 
				tmp=max(tmp,t*b[sta[head[i]]].k+b[sta[head[i]]].b);
			}
		}
	}
	if(tmp==-1e18) puts("baigei");
	else write(tmp),puts("");
}

int main()
{
	n=read();m=read();
	len=sqrt(n/log2(n))+1;
//	len=144;
	for(re int i=1;i<=n;++i)
		pos[i]=(i-1)/len+1;
	for(re int i=1;i<=pos[n];++i)
		L[i]=R[i-1]+1,R[i]=R[i-1]+len;
	R[pos[n]]=n;
	while(m--)
	{
		opt=read();
		if(opt==1)
		{
			int T=read(),x=read(),z=read(),s=read();
			vis[x]=1;use[pos[x]]=1;//这个点有,这个块有 
			need[pos[x]]=1;//需要重构 
			a[x].k=z;a[x].b=s-(ll)T*z;
		}
		else 
		{
			int T=read(),l=read(),r=read();
			doit(T,l,r);
		}
	}
	return 0;	
}


posted @ 2021-03-14 22:49  your_棒棒糖  阅读(132)  评论(0编辑  收藏  举报