第八届蓝桥杯(软件类)C++省赛A组真题题解

题目链接

A组真题

题目结构

题目类型分值
第一题结果填空5分
第二题结果填空11分
第三题结果填空13分
第四题结果填空17分
第五题代码填空7分
第六题代码填空9分
第七题程序设计19分
第八题程序设计21分
第九题程序设计23分
第十题程序设计25分

第一题 迷宫

  • 问题重现

    X星球的一处迷宫游乐场建在某个小山坡上。
    它是由10x10相互连通的小房间组成的。

    房间的地板上写着一个很大的字母。
    我们假设玩家是面朝上坡的方向站立,则:
    L表示走到左边的房间,
    R表示走到右边的房间,
    U表示走到上坡方向的房间,
    D表示走到下坡方向的房间。

    X星球的居民有点懒,不愿意费力思考。
    他们更喜欢玩运气类的游戏。这个游戏也是如此!

    开始的时候,直升机把100名玩家放入一个个小房间内。
    玩家一定要按照地上的字母移动。

    迷宫地图如下:

    UDDLUULRUL
    UURLLLRRRU
    RRUURLDLRD
    RUDDDDUUUU
    URUDLLRRUU
    DURLRLDLRL
    ULLURLLRDU
    RDLULLRDDD
    UUDDUDUDLL
    ULRDLUURRR

    请你计算一下,最后,有多少玩家会走出迷宫?
    而不是在里边兜圈子。

    注意

    请提交该整数,表示走出迷宫的玩家数目,不要填写任何多余的内容。

    如果你还没明白游戏规则,可以参看一个简化的4x4迷宫的解说图:
    在这里插入图片描述

  • 解题思路
    对迷宫中的每一个点都跑一遍 d f s dfs dfs判断是否能够到达边界 ,注意对于每一个点在开始搜索之前都需要情况状态,并在搜索过程中标记访问过的点避免死循环。

  • 代码

/**
  *@filename:迷宫
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-26 10:19
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;

//我们可以dfs每个点。
bool vis[11][11];//判断该点是否已经访问。
string graph[11];
bool flag;//判断是否已经逃出迷宫。
void dfs(int x,int y){
    if(x<0||x>=10||y<0||y>=10){
        flag=true;
        return;
    }
    vis[x][y]=true;
    if(graph[x][y]=='L'&&!vis[x][y-1]){
        dfs(x,y-1);
    }
    else if(graph[x][y]=='R'&&!vis[x][y+1]){
        dfs(x,y+1);
    }
    else if(graph[x][y]=='U'&&!vis[x-1][y]){
        dfs(x-1,y);
    }
    else if(graph[x][y]=='D'&&!vis[x+1][y]){
        dfs(x+1,y);
    }
}
void solve(){
    int ans=0;
    for(int i=0;i<10;i++){
        for(int j=0;j<10;j++){
            memset(vis,false,sizeof(vis));
            flag=false;
            dfs(i,j);
            if(flag)ans++;
        }
    }
    cout<<ans<<endl;
}
int main() {
    for(int i=0;i<10;i++){
        cin>>graph[i];
    }
    solve();
    return 0;
}
  • 答案

    31 31 31


第二题 跳蚱蜢

  • 问题重现

    如图所示:
    在这里插入图片描述

    有9只盘子,排成1个圆圈。
    其中8只盘子内装着8只蚱蜢,有一个是空盘。
    我们把这些蚱蜢顺时针编号为 1~8

    每只蚱蜢都可以跳到相邻的空盘中,
    也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。

    请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列,
    并且保持空盘的位置不变(也就是1-8换位,2-7换位,…),至少要经过多少次跳跃?

    注意

    要求提交的是一个整数,请不要填写任何多余内容或说明文字。

  • 解题思路

    对于这道题,==我们需要将空格作为一个动点,它可以进行四种操作,由于我们需要寻找最少的操作次数,所以我们可以利用 b f s bfs bfs模拟交换。==为了避免状态重复我们可以利用 m a p map map容器来实现,记录已经出现过的地图状态。值得注意的一点就是坐标的处理 ,我们如果坐标大于 8 8 8就需要对 8 8 8取余,而如果小于 0 0 0,则需要加上 9 9 9这样才可以保证逻辑上是环状的。

  • 代码

/**
  *@filename:跳蚱蜢
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-26 10:46
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;

map<string,int> p;//存储访问过的状态。
//将空格作为动点。由于要寻找最少跳跃,我们需要进行bfs。
struct node{
    int x;
    int step;
    string graph;//记录当前状态的地图。
};
string res;
string result="087654321";
void bfs(){
    node head,temp;
    queue<node> q;
    head.x=0,head.step=0,head.graph=res;
    p[head.graph]++;
    q.push(head);
    while(!q.empty()){
        head=q.front();
        q.pop();
        //接下来开始模拟四种操作。
        if(head.graph==result){
            cout<<head.step<<endl;
            return;
        }
        for(int i=0;i<4;i++){
            if(i==0){
                //左跳。
                temp.x=head.x+1;
                //此时需要判断temp坐标是否符合。
                if(temp.x>8){
                    temp.x=0;
                }
            }
            else if(i==1){
                //跨越跳。
                temp.x=head.x+2;
                if(temp.x>8){
                    temp.x=(temp.x-1)%8;
                }
            }
            else if(i==2){
                temp.x=head.x-1;
                if(temp.x<0){
                    temp.x=8;
                }
            }
            else{
                temp.x=head.x-2;
                if(temp.x<0){
                    temp.x=9+temp.x;
                }
            }
            temp.graph=head.graph,temp.step=head.step+1;
             //交换这两点。
            swap(temp.graph[head.x],temp.graph[temp.x]);
            if(p[temp.graph])continue;
            p[temp.graph]++;
            q.push(temp);
        }
    }
}
void solve(){
    bfs();
}
int main() {
    res="012345678";
    solve();
    return 0;
}
  • 答案

    20 20 20


第三题 魔方状态

  • 问题重现

    二阶魔方就是只有2层的魔方,只由8个小块组成。
    如图所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbwBJgHV-1617119048901)(第八届蓝桥杯(软件类)省赛A组.assets/image-20210330205506193.png)]

    小明很淘气,他只喜欢3种颜色,所有把家里的二阶魔方重新涂了颜色,如下:

    前面:橙色
    右面:绿色
    上面:黄色
    左面:绿色
    下面:橙色
    后面:黄色

    请你计算一下,这样的魔方被打乱后,一共有多少种不同的状态。

    如果两个状态经过魔方的整体旋转后,各个面的颜色都一致,则认为是同一状态。

    注意

    请提交表示状态数的整数,不要填写任何多余内容或说明文字。

  • 解题思路

    一开始拿到这道题的时候是真的不知道该怎么处理,直到找到一个狠人的博客才把这道题彻彻底底的解决了。博客链接

    这道题说实话不是一般的难,我们需要将所有的情况都找出,那么只能模拟操作进行判重。对于魔方而言,只做顶层顺时针U操作,前层顺时针F操作,右层顺时针R操作即可得到所有状态。

    为了方便,我们需要进行一些处理,对于二阶魔方上的小块,我们可以对其进行编号,如下:

在这里插入图片描述

而每个小块有六个面,我们可以用字符串表示该块的状态。对于看不见的面,我们用黑色来表示(因为我们终究是要表示这些块,加上则更方便进行处理,对于其中的看不见的面我们不会处理到。)则o表示橙色,b表示黑色,g表示绿色,y表示黄色。我们规定小块上的面编号如下:

img

那么初始状态如下:

 string cube[8]={"oybbgb","oygbbb","bygbby","bybbgy",
 "obbogb","obgobb",
 "bbgoby","bbbogy"};//代表魔方的初始状态。

要注意的就是模拟 R , U , F R,U,F R,U,F这三个操作了,我们可以大量利用 s w a p swap swap函数来进行交换,先对小块上的面进行交换,然后再处理块。这些注意好细节就好处理,同样,为了之后判重整体旋转魔方,所以我们还需要写三个操作进行底层旋转利用判重。

接下来我们需要处理的就是判重了,对于一个魔方,这是一个二维的字符串,所以我们需要将其降维,将其拼接起来并用 s e t set set容器存储。那么在检查是否出现我们就需要对魔方进行整体旋转了,然后将没有重复的魔方加入 s e t set set中。

最后,也是一个非常细节的问题,就是我们需要用头尾指针来记录已经出现过的魔方数和当时正在旋转的魔方。这样我们总能将所有的魔方都遍历到,而不至于出现只进行 R U F R U F . . . RUFRUF... RUFRUF...,我们知道这种不过多久就会还原成初始魔方。

结果需要跑挺久的,大概 1 1 1分钟,所以这道题出在这实属有些不合适,虽然这道题挺有趣的

  • 代码
/**
  *@filename:魔方状态
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-26 20:46
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2000000 + 5;
const int mod = 1e9+7;

string cube[8]={"oybbgb","oygbbb","bygbby","bybbgy","obbogb","obgobb","bbgoby","bbbogy"};//代表魔方的初始状态。
set<string> unique_cube;//去重容器。
void uCell(string &s){
    swap(s[0],s[2]);
    swap(s[2],s[5]);
    swap(s[5],s[4]);
}
void fCell(string &s){
    swap(s[2],s[1]);
    swap(s[1],s[4]);
    swap(s[4],s[3]);
}
void rCell(string &s){
    swap(s[1],s[0]);
    swap(s[0],s[3]);
    swap(s[3],s[5]);
}
void uFront(vector<string> &cube){
    //进行顶层顺时针转动。
    uCell(cube[0]);
    uCell(cube[1]);
    uCell(cube[2]);
    uCell(cube[3]);
    swap(cube[0],cube[1]);
    swap(cube[1],cube[2]);
    swap(cube[2],cube[3]); 
}
void uTail(vector<string> &cube){
    //进行底层顺时针转动。
    uCell(cube[4]);
    uCell(cube[5]);
    uCell(cube[6]);
    uCell(cube[7]);
    swap(cube[4],cube[5]);
    swap(cube[5],cube[6]);
    swap(cube[6],cube[7]);
}
void fFront(vector<string> &cube){
    //进行前层顺时针转动。
    fCell(cube[0]);
    fCell(cube[1]);
    fCell(cube[4]);
    fCell(cube[5]);
    swap(cube[1],cube[0]);
    swap(cube[0],cube[4]);
    swap(cube[4],cube[5]);
}
void fTail(vector<string> &cube){
    //进行后层顺时针旋转。
    fCell(cube[2]);
    fCell(cube[3]);
    fCell(cube[6]);
    fCell(cube[7]);
    swap(cube[2],cube[3]);
    swap(cube[3],cube[7]);
    swap(cube[7],cube[6]);
}
void rFront(vector<string> &cube){
    //进行右层顺时针旋转。
    rCell(cube[1]);
    rCell(cube[2]);
    rCell(cube[5]);
    rCell(cube[6]);
    swap(cube[2],cube[1]);
    swap(cube[1],cube[5]);
    swap(cube[5],cube[6]);
}
void rTail(vector<string> &cube){
    //进行左层顺时针旋转。
    rCell(cube[0]);
    rCell(cube[3]);
    rCell(cube[4]);
    rCell(cube[7]);
    swap(cube[3],cube[0]);
    swap(cube[0],cube[4]);
    swap(cube[4],cube[7]);
}
string change_str(vector<string> cube){
    //将二维转化为一维,便于判重。
    string temp;
    for(int i=0;i<8;i++){
        temp+=cube[i];
    }
    return temp;
}
bool check_unique(vector<string> cube){
    //判断是否重复。整体旋转魔方。
    for(int i=0;i<4;i++){
        uFront(cube),uTail(cube);
        for(int j=0;j<4;j++){
            fFront(cube),fTail(cube);
            for(int k=0;k<4;k++){
                rFront(cube),rTail(cube);
                if(unique_cube.count(change_str(cube))==1){
                    return false;
                }
            }
        }
    }
    //说明没有找到,那么我们就并入判重容器。
    unique_cube.insert(change_str(cube));
    return true;
}
void solve(){
    vector<vector<string>> cubes(maxn,vector<string>(8)); 
    copy(cube,cube+8,cubes[0].begin());
    int frontCube=0,tailCube=1;
    unique_cube.insert(change_str(cubes[0]));
    while(frontCube!=tailCube){
        for(int i=0;i<3;i++){
            copy(cubes[frontCube].begin(),cubes[frontCube].end(),cubes[tailCube].begin());
            if(i==0){
                uFront(cubes[tailCube]);
                if(check_unique(cubes[tailCube])){
                    tailCube++;
                }
            }
            else if(i==1){
                fFront(cubes[tailCube]);
                if(check_unique(cubes[tailCube])){
                    tailCube++;
                }
            }
            else{
                rFront(cubes[tailCube]);
                if(check_unique(cubes[tailCube])){
                    tailCube++;
                }
            }
        }
        frontCube++;
    }
    cout<<tailCube<<endl;
}
int main() {
    solve();
    return 0;
}
  • 答案

    229878 229878 229878


第四题 方格分割

  • 问题重现

    6x6的方格,沿着格子的边线剪开成两部分。
    要求这两部分的形状完全相同。

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

    就是可行的分割法。

    试计算:
    包括这3种分法在内,一共有多少种不同的分割方法。

  • 解题思路

    这道题已经告诉我们是沿着边线分割了,我们也应该知道,如果对方格进行处理要明显不好做。我们如果要分割后的两部分都相同,那么它们必须是中心点对称的。也就是经过了点 ( 3 , 3 ) (3,3) (3,3)。所以我们可以把这个点当做出发点,那么我们对这个点开始进行 d f s dfs dfs,在搜索过程中,我们需要对中心对称的点进行标记,为了避免干扰,在当前路径搜索完成之后再将标记还原。那么搜索完成的条件即是到达边界,这样正是将方格分割成了两个完全相等的部分。对于最后的结果,我们需要除以 4 4 4,我们且看下图:

在这里插入图片描述

这四条路径分出来的形状是一样的。这是因为正方形是高度对称的,而分割路径则可以放置在竖直两条和水平两条。也就是 4 4 4种情况。

  • 代码
/**
  *@filename:方格分割
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-27 08:46
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;

int go[][2]={1,0,-1,0,0,1,0,-1};
bool vis[7][7];
//我们沿着点进行分割即可。由于存在着堆成,所以我们对最后的结果必须除以4.
int ans;
void dfs(int x,int y){
    if(x==0||y==0||x==6||y==6){
        ans++;
        return;
    }
    for(int i=0;i<4;i++){
        int tx=x+go[i][0];
        int ty=y+go[i][1];
        //将对称点也需要标记上。
        if(vis[tx][ty])continue;
        vis[tx][ty]=vis[6-tx][6-ty]=true;
        dfs(tx,ty);
        vis[tx][ty]=vis[6-tx][6-ty]=false;
    }
}
void solve(){
    memset(vis,false,sizeof(vis));
    vis[3][3]=true;
    ans=0;
    dfs(3,3);
    cout<<ans/4<<endl;
}
int main() {
    solve();
    return 0;
}
  • 答案

    509 509 509


第五题 字母组串

  • 问题重现

    由 A,B,C 这3个字母就可以组成许多串。
    比如:“A”,“AB”,“ABC”,“ABA”,“AACBB” …

    现在,小明正在思考一个问题:
    如果每个字母的个数有限定,能组成多少个已知长度的串呢?

    他请好朋友来帮忙,很快得到了代码,
    解决方案超级简单,然而最重要的部分却语焉不详。

    请仔细分析源码,填写划线部分缺少的内容。

    #include <stdio.h>
    
    // a个A,b个B,c个C 字母,能组成多少个不同的长度为n的串。
    int f(int a, int b, int c, int n)
    {
     if (a < 0 || b < 0 || c < 0) return 0;
     if (n == 0) return 1;
    
     return ______________________________________;  // 填空
    }
    
    int main()
    {
     printf("%d\n", f(1, 1, 1, 2));
     printf("%d\n", f(1, 2, 3, 3));
     return 0;
    }
    
    
  • 解题思路

    一道非常简单的代码填空问题。因为这几个参数的含义题目已经告诉我们了,即:在当前状态还剩下 n n n个位置没有填充,还有 a a a A A A b b b B B B c c c C C C可以用。我们发现,这实际上就是一个递归回溯,将大问题转换为小问题,而结束条件即是当 n n n已经为 0 0 0或者 A , B , C A,B,C A,B,C的数量不够用了。那么我们每次可以选择用 a a a b b b c c c,则答案已经清晰明了了。

  • 答案

f(a-1,b,c,n-1)+f(a,b-1,c,n-1)+f(a,b,c-1,n-1)

第六题 最大公共子串

  • 问题重现

    最大公共子串长度问题就是:
    求两个串的所有子串中能够匹配上的最大长度是多少。

    比如:“abcdkkk” 和 “baabcdadabc”,
    可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。

    下面的程序是采用矩阵法进行求解的,这对串的规模不大的情况还是比较有效的解法。

    请分析该解法的思路,并补全划线部分缺失的代码。

    #include <stdio.h>
    #include <string.h>
    
    #define N 256
    int f(const char* s1, const char* s2)
    {
    int a[N][N];
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    int i,j;
    
    memset(a,0,sizeof(int)*N*N);
    int max = 0;
    for(i=1; i<=len1; i++){
    	for(j=1; j<=len2; j++){
    		if(s1[i-1]==s2[j-1]) {
    			a[i][j] = __________________________;  //填空
    			if(a[i][j] > max) max = a[i][j];
    		}
    	}
    }
    
    return max;
    }
    
    int main()
    {
    printf("%d\n", f("abcdkkk", "baabcdadabc"));
    return 0;
    }
    
    
  • 解题思路

    基础 d p dp dp问题。这里进行状态转移。即 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + 1 dp[i][j]=dp[i-1][j-1]+1 dp[i][j]=dp[i1][j1]+1

  • 答案

a[i-1][j-1]+1

第七题 正则问题

  • 问题重现

    考虑一种简单的正则表达式:
    只由 x ( ) | 组成的正则表达式。
    小明想求出这个正则表达式能接受的最长字符串的长度。

    例如 ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是: xxxxxx,长度是6。

    输入格式

    一个由x()|组成的正则表达式。输入长度不超过100,保证合法。

    输出格式

    这个正则表达式能接受的最长字符串的长度。

    样例输入

    ((xx|xxx)x|(x|xx))xx

    样例输出

    6

    资源约定

    峰值内存消耗(含虚拟机) < 256M
    CPU消耗 < 1000ms

    注意

    请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

    main函数需要返回0;
    只使用ANSI C/ANSI C++ 标准;
    不要调用依赖于编译环境或操作系统的特殊函数。
    所有依赖的函数必须明确地在源文件中 #include
    不能通过工程设置而省略常用头文件。
    提交程序时,注意选择所期望的语言类型和编译器类型。

  • 解题思路

    我们首先要知道对于 ( ) () ()符号是优先计算的,而对于 ∣ | 符号则是取左右两边的其中一个。也就是说当我们碰到 ( ( (的时候,我们就需要开始遍历新的路统计’x’的数量,然后碰到 ) ) )的时候则需要返回原来的路径,并将其值累加。同样对于 ∣ | 符号也是需要开启一条新路,结束的标志则是遍历完成或者是遇到了 ) ) ),在遍历之前和遍历之后返回的数之间去一个最大值。清楚了这个,我们就可以进行 d f s dfs dfs模拟了,我们从字符串开始进行。这里引用一张大佬画的递归搜索树,原博文地址为:博客链接

在这里插入图片描述

  • 代码
/**
  *@filename:正则问题
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-27 10:17
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;

int len;
int cur;
string s;
//我们知道对于(是具有优先计算的,而dfs其实就可以看成是一次计算,那么遇到(
//即是优先计算,我们进去dfs搜索即可。
int dfs(){
    int ans=0;
    //在while循环下执行当前计算。
    while(cur<len){
        if(s[cur]=='('){
            //优先计算。
            cur++;
            ans+=dfs();//结束完这次计算后我们继续往后遍历。
            cur++;
        }
        else if(s[cur]==')'){
            //说明优先计算结束,我们直接break即可。
            break;
        }
        else if(s[cur]=='|'){
            //说明遇到分支,我们继续往下。
            cur++;
            ans=max(ans,dfs());
        }
        else{
            cur++;
            ans++;
        }
    }
    return ans;
}
int main() {
    while(cin>>s){
        len=s.size();
        cur=0;
        cout<<dfs()<<endl;
    }
    return 0;
}

第八题 包子凑数

  • 问题重现

    小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子。每种蒸笼都有非常多笼,可以认为是无限笼。

    每当有顾客想买X个包子,卖包子的大叔就会迅速选出若干笼包子来,使得这若干笼中恰好一共有X个包子。比如一共有3种蒸笼,分别能放3、4和5个包子。当顾客想买11个包子时,大叔就会选2笼3个的再加1笼5个的(也可能选出1笼3个的再加2笼4个的)。

    当然有时包子大叔无论如何也凑不出顾客想买的数量。比如一共有3种蒸笼,分别能放4、5和6个包子。而顾客想买7个包子时,大叔就凑不出来了。

    小明想知道一共有多少种数目是包子大叔凑不出来的。

    输入格式

    第一行包含一个整数N。(1 <= N <= 100)
    以下N行每行包含一个整数Ai。(1 <= Ai <= 100)

    输出格式

    一个整数代表答案。如果凑不出的数目有无限多个,输出INF。

    样例输入

    2
    4
    5

    样例输出

    6

    样例输入

    2
    4
    6

    样例输出

    INF

    样例解释

    对于样例1,凑不出的数目包括:1, 2, 3, 6, 7, 11。
    对于样例2,所有奇数都凑不出来,所以有无限多个。

    资源约定

    峰值内存消耗(含虚拟机) < 256M
    CPU消耗 < 1000ms

    注意

    请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

    main函数需要返回0;
    只使用ANSI C/ANSI C++ 标准;
    不要调用依赖于编译环境或操作系统的特殊函数。
    所有依赖的函数必须明确地在源文件中 #include
    不能通过工程设置而省略常用头文件。
    提交程序时,注意选择所期望的语言类型和编译器类型。

  • 解题思路

    这道题实际上就是先利用贝祖定理判断再利用动态规划解决。我们首先要知道贝祖定理:任意两个数的组合必定是他们 g c d gcd gcd的倍数,同样可以推广到更多数:如果这些数的 g c d gcd gcd d d d,那么他们的组合是 d d d的倍数,如果 d d d不是 1 1 1,那么必然有无限个数无法被组合出来。这里指路一篇 b l o g blog blog学习贝祖定理。博客链接。那么我们判断是否有无数解,只需要判断它们的 g c d gcd gcd是否为 1 1 1即可。那么对于为 1 1 1的情况,即其中的所有数都互质,而我们知道,两个互质数 a , b a,b a,b最大不能表示的为: ( a − 1 ) ( b − 1 ) − 1 (a-1)(b-1)-1 (a1)(b1)1,也就是说我们上界只需要取最大的两个互质数乘积,我们可以选择 10000 10000 10000作为上界。

    接下来,我们来定义状态,对于 d p [ i ] [ j ] dp[i][j] dp[i][j],我们表示已经用了 i i i个笼子,且需要 j j j个包子能否被凑出来。则我们可以根据在取第 i i i个笼子的时候来判断第 i i i个物品取了多少件。若有一件是可以被凑出的,那么则成立。也就是说。 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] ∣ ∣ d p [ i ] [ j − w [ i ] ] ∣ ∣ d p [ i ] [ j − 2 ∗ w [ i ] ] . . . . . . . . dp[i][j]=dp[i-1][j]||dp[i][j-w[i]]||dp[i][j-2*w[i]]........ dp[i][j]=dp[i1][j]dp[i][jw[i]]dp[i][j2w[i]]........我们检索即可。最后我们累加在 10000 10000 10000以内不能凑出来的数。

  • 代码

/**
  *@author: pursuit
  *@Created: 2021-03-28 13:39
**/
#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn=100+5;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;

//动态规划,我们可以表示dp[i][j]表示为只取前i个笼子,j是否能够被凑出。
//则我们可以根据在取第i个笼子的时候来判断第i个物品取了多少件。若有一件是可以被凑出的,那么则成立。
//也就是说。dp[i][j]=dp[i-1][j]||dp[i][j-w[i]]||dp[i][j-2*w[i]]........我们检索即可。
//注意判断gcd是否为1。  

int n;
int dp[maxn][10005];
int a[maxn];
int Gcd(int n,int m){
    return m?Gcd(m,n%m):n;
}
void solve(){
    //背包问题。
    memset(dp,0,sizeof(dp));
    dp[0][0]=true;//初始状态设置好。
    for(int i=1;i<=n;i++){
        for(int j=0;j<=10000;j++){
            dp[i][j]=dp[i-1][j];
            if(j>=a[i]){
                dp[i][j]=dp[i][j]||dp[i][j-a[i]];
            }
        }
    }
    int ans=0;
    for(int j=0;j<=10000;j++){
        if(!dp[n][j]){
            ans++;
        }
    }
    cout<<ans<<endl;
}
int main(){
    while(cin>>n){
        int gcd=0;
        for(int i=1;i<=n;i++){
            cin>>a[i];
            gcd=Gcd(gcd,a[i]);
        }
        if(gcd!=1){
            cout<<"INF"<<endl;
            continue;
        }
        solve();
    }
    return 0;
}

第九题 分巧克力

  • 问题重现

    儿童节那天有K位小朋友到小明家做客。小明拿出了珍藏的巧克力招待小朋友们。
    小明一共有N块巧克力,其中第i块是Hi x Wi的方格组成的长方形。

    为了公平起见,小明需要从这 N 块巧克力中切出K块巧克力分给小朋友们。切出的巧克力需要满足:

    形状是正方形,边长是整数
    大小相同
    例如一块6x5的巧克力可以切出6块2x2的巧克力或者2块3x3的巧克力。

    当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

    输入格式

    第一行包含两个整数N和K。(1 <= N, K <= 100000)
    以下N行每行包含两个整数Hi和Wi。(1 <= Hi, Wi <= 100000)
    输入保证每位小朋友至少能获得一块1x1的巧克力。

    输出格式

    输出切出的正方形巧克力最大可能的边长。

    样例输入

    2 10
    6 5
    5 6

    样例输出

    2

    资源约定

    峰值内存消耗(含虚拟机) < 256M
    CPU消耗 < 1000ms

    注意

    请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

    main函数需要返回0;
    只使用ANSI C/ANSI C++ 标准;
    不要调用依赖于编译环境或操作系统的特殊函数。
    所有依赖的函数必须明确地在源文件中 #include
    不能通过工程设置而省略常用头文件。
    提交程序时,注意选择所期望的语言类型和编译器类型。

  • 解题思路

    由于结果一定是在 1 1 1~ 100000 100000 100000的,所以我们可以枚举这切出来的巧克力边长,这里利用二分法。简单易得。

  • 代码

/**
  *@filename:分巧克力
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-30 19:08
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;

int n,k;
struct node{
    int h,w;
};
node chocolate[maxn];
//二分法枚举我们的最大边长。
bool check(int a){
    //判断是否满足条件。
    int ans=0;
    for(int i=0;i<n;i++){
        ans+=(chocolate[i].h/a)*(chocolate[i].w/a);
        if(ans>=k)return true;
    }
    return false;
}
void solve(){
    int l=1,r=maxn;
    while(l<r){
        //cout<<l<<" "<<r<<endl;
        int mid=(l+r+1)>>1;//注意这里一定要+1,再进行除2,避免减小mid的值。
        if(check(mid)){
            l=mid;//往右区间枚举。
        }
        else{
            r=mid-1;//往左区间枚举。
        }
    }
    cout<<l<<endl;
}
int main() {
    while(cin>>n>>k){
        for(int i=0;i<n;i++){
            cin>>chocolate[i].h>>chocolate[i].w;
        }
        solve();
    }
    return 0;
}

第十题 油漆面积

  • 问题重现

    X星球的一批考古机器人正在一片废墟上考古。
    该区域的地面坚硬如石、平整如镜。
    管理人员为方便,建立了标准的直角坐标系。

    每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。
    经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。

    矩形的表示格式为(x1,y1,x2,y2),代表矩形的两个对角点坐标。

    为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。
    小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。

    其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。
    注意,各个矩形间可能重叠。

    本题的输入为若干矩形,要求输出其覆盖的总面积。

    输入格式

    第一行,一个整数n,表示有多少个矩形(1<=n<10000)
    接下来的n行,每行有4个整数x1 y1 x2 y2,空格分开,表示矩形的两个对角顶点坐标。
    (0<= x1,y1,x2,y2 <=10000)

    输出格式

    一行一个整数,表示矩形覆盖的总面积。

    样例输入

    3
    1 5 10 10
    3 1 20 20
    2 7 15 17

    样例输出

    340

    样例输入

    3
    5 2 10 6
    2 7 12 10
    8 1 15 15

    样例输出

    128

    资源约定

    峰值内存消耗(含虚拟机) < 256M
    CPU消耗 < 2000ms

    注意

    请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

    main函数需要返回0;
    只使用ANSI C/ANSI C++ 标准;
    不要调用依赖于编译环境或操作系统的特殊函数。
    所有依赖的函数必须明确地在源文件中 #include
    不能通过工程设置而省略常用头文件。
    提交程序时,注意选择所期望的语言类型和编译器类型。

  • 解题思路

    这道题直接暴力是容易想到的,我们以左下角的点代表了一个单位正方形,那么对于给定的矩形,我们只要将其内部的点置为真即可。最后遍历统计所有的点。这种方法只能水一点点的分,真正的解法应该是扫描线算法,这个算法暂时不会,之后再补,先贴大佬代码。

  • 暴力代码

/**
  *@filename:油漆面积
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-30 23:19
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 10000 + 5;
const int mod = 1e9+7;

int n;
int min_x,min_y,max_x,max_y;
bool vis[maxn][maxn];
void solve(){
    int ans=0;
    for(int i=min_x;i<=max_x;i++){
        for(int j=min_y;j<=max_y;j++){
            ans+=vis[i][j];
        }
    }
    cout<<ans<<endl;
}
int main() {
    int x1,y1,x2,y2;
    while(cin>>n){
        min_x=100000,min_y=100000,max_x=0,max_y=0;
        memset(vis,false,sizeof(vis));
        for(int i=0;i<n;i++){
            cin>>x1>>y1>>x2>>y2;
            min_x=min(min(x1,x2),min_x);
            min_y=min(min(y1,y2),min_y);
            max_x=max(max(x1,x2),max_x);
            max_y=max(max(y1,y2),max_y);
            for(int i=x1;i<x2;i++){
                for(int j=y1;j<y2;j++){
                    vis[i][j]=true;
                }
            }
        }
        solve();
    } 
    return 0;
}
  • 扫描线算法代码
/*
线段树优化的扫描线,时间复杂度为 O(N log N)
*/
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 1e4 + 10;

struct Segment {
    int x, y1, y2;
    int k;
    bool operator<(const Segment& b) {
        return x < b.x;
    }
} seg[N * 2];

struct SegmentTree {
    int l, r;
    // 该节点自身被覆盖的次数
    int cnt;
    // 该节点代表的区间被矩形覆盖的长度
    int len;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define cnt(x) tree[x].cnt
#define len(x) tree[x].len
} tree[N * 4];

void build(int p, int l, int r) {
    l(p) = l, r(p) = r;
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(2 * p, l, mid);
    build(2 * p + 1, mid + 1, r);
}

void pushup(int p) {
    if (cnt(p) > 0)
        len(p) = r(p) - l(p) + 1;
    else {
        if (l(p) == r(p))
            len(p) = 0;
        else
            len(p) = len(2 * p) + len(2 * p + 1);
    }
}

void change(int p, int l, int r, int k) {
    // 完全覆盖
    if (l <= l(p) && r >= r(p)) {
        cnt(p) += k;
        pushup(p);
        return;
    }
    int mid = (l(p) + r(p)) >> 1;
    // 和左子节点有相交部分
    if (l <= mid) change(2 * p, l, r, k);
    // 和右子节点有相交部分
    if (r >= mid + 1) change(2 * p + 1, l, r, k);
    pushup(p);
}

int n, x1, x2, y1, y2;
int main() {
    cin >> n;

    for (int i = 1; i <= n; i++) {
        cin >> x1 >> y1 >> x2 >> y2;
        // 左边为入边,k = 1
        seg[2 * i - 1].x = x1, seg[2 * i - 1].y1 = y1, seg[2 * i - 1].y2 = y2, seg[2 * i - 1].k = 1;
        // 右边为出边,k = -1
        seg[2 * i].x = x2, seg[2 * i].y1 = y1, seg[2 * i].y2 = y2, seg[2 * i].k = -1;
    }
    // 出入边均按照 x 坐标从小到大排序
    sort(seg + 1, seg + 1 + 2 * n);
    int ans = 0;
    build(1, 0, N);
    for (int i = 1; i <= 2 * n; i++) {
        // 计算扫描线在竖直方向上的长度
        int len = len(1);
        ans += len * (seg[i].x - seg[i - 1].x);

        // 注意这里是 seg[i].y2 - 1,因为竖直方向总长度是 y2 - y1
        change(1, seg[i].y1, seg[i].y2 - 1, seg[i].k);
    }
    cout << ans << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HeZephyr

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值