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

C组真题(未列举出的题同A组真题相同,已给出A组真题题解链接)

题目结构

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

注意:未列举出的题同A组真题相同,请自行前往A组真题题解查阅,博客链接

第一题 贪吃蛇长度

  • 问题重现

    小明在爷爷的私人收藏馆里找到一台老式电脑。居然没有图形界面,只能用控制台编程。
    经过小明的一阵摸索,神奇地设计出了控制台上的贪食蛇游戏。
    如下图,是游戏时画面截图。

    +-------------------------------------------------+
    |                                                 |
    |    H######                      ####            |
    |          #                      #  #            |
    |          #                      #  #            |
    |          #     ####             #  #            |
    |          #     #  #             #  #            |
    |          ######@###             #  #            |
    |                #       ####     #  #            |
    |                #       #  #     #  #            |
    |            ####@#######@###     #  #            |
    |            #   #       #        #  #            |
    | T          #####       #        #  #   ##       |
    | #                      #      ###  ### ##       |
    | ################       #      #      ####       |
    |                #       #      #         #       |
    |   ##############       #######@##########       |
    |   #                         ###                 |
    |   ###########################                   |
    +-------------------------------------------------+
    

    其中,H表示蛇头,T表示蛇尾。#表示蛇的身体,@表示身体交叉重叠的地方。
    你能说出现在的贪吃蛇长度是多少吗?
    其实,只要数出#的数目算1,数出@的数目,算2,再加上头尾各算1就计算好了
    人工数一下?太累眼睛了,聪明的你为什么不让计算机帮忙呢?
    本题的要求就是: 请填写上图中贪食蛇的长度是多少?

    输出

    输出一个整数表示答案

  • 解题思路

    读入数据利用getline函数或者gets函数。然后直接遍历统计即可。

  • 代码

/**
  *@filename:贪吃蛇长度
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-31 21:52
**/
#include <bits/stdc++.h>

using namespace std;

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

//读入数据即可,利用getline函数。
string temp;
int cnt;
void solve(){
    for(int i=0;i<temp.size();i++){
        if(temp[i]=='#'){
            cnt++;
        }
        else if(temp[i]=='@'){
            cnt+=2;//重合部分有2个。
        }
    }
}
int main() {
    cnt=2;//蛇头蛇尾。
    while(getline(cin,temp)){
        if(temp=="exit"){
            break;
        }
        solve();
    }
    cout<<cnt<<endl;//190
    return 0;
}
  • 答案

    190 190 190


第二题 兴趣小组

  • 问题重现

    为丰富同学们的业余文化生活,某高校学生会创办了3个兴趣小组(以下称A组,B组,C组)。
    每个小组的学生名单分别在【A.txt】,【B.txt】和【C.txt】中。
    每个文件中存储的是学生的学号。
    由于工作需要,我们现在想知道:
    既参加了A组,又参加了B组,但是没有参加C组的同学一共有多少人?

    输入
    A.txt
    B.txt
    C.txt

    输出

    输出一个整数表示答案

  • 解题思路

    由于这些数据之间是用逗号分隔的,所以我们先要将这些数据做一下处理,在文本编辑器中利用 C t r l + H Ctrl+H Ctrl+H功能将逗号替换,这样,处理过的数据就可以直接拿来用来,为了判断输入终止,且我们不知道该输入多少的,所以我们可以采用while循环输入并利用输入值等于 0 0 0来终止,这是一些小技巧。那么我们会想到用 s e t set set容器来处理这些数据,因为 s e t set set容器内置find函数正好可以处理查找功能。故此题易解。

  • 代码

/**
  *@filename:兴趣小组
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-03-31 21:58
**/
#include <bits/stdc++.h>

using namespace std;

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

set<int> a,b,c;//存放数据。
void solve(){
    int cnt=0;
    for(auto &x:a){
        if(x!=0&&b.find(x)!=b.end()&&c.find(x)==c.end()){
            cnt++;
        }
    }
    cout<<cnt<<endl;
}
int main() {
    int temp; 
    while(cin>>temp&&temp!=0){
        a.insert(temp);
    } 
    while(cin>>temp&&temp!=0){
        b.insert(temp);
    }
    while(cin>>temp&&temp!=0){
        c.insert(temp);
    }
    solve();//20
    return 0;
}
  • 答案

    20 20 20


第三题 算式900

  • 问题重现

    小明的作业本上有道思考题:算式:$ (□□□□-□□□□)*□□=900$
    其中的小方块代表09的数字,这10个方块刚好包含了09中的所有数字。
    注意:0不能作为某个数字的首位。
    小明经过几天的努力,终于做出了答案!如下:(5012-4987)*36=900
    用计算机搜索后,发现还有另外一个解,本题的任务就是:请你算出这另外的一个解。

    输出

    输出格式需要与示例严格一致;
    括号及运算符号不要用中文输入法;
    整个算式中不能包含空格。

  • 解题思路

    对于数据量小的,且不能太好的剪枝,我们可以直接暴力全排列枚举。这样做非常省时间且不容易错。

  • 代码

/**
  *@filename:算式900
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-04-01 08:55
**/
#include <bits/stdc++.h>

using namespace std;

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

//直接暴力全排列输出所有解。
int a[]={0,1,2,3,4,5,6,7,8,9};
void solve(){
    do{
        if(a[0]==0||a[4]==0||a[8]==0)continue;
        if(((a[0]*1000+a[1]*100+a[2]*10+a[3])-(a[4]*1000+a[5]*100+a[6]*10+a[7]))*(a[8]*10+a[9])==900){
            cout<<"("<<a[0]*1000+a[1]*100+a[2]*10+a[3]<<"-"
            <<a[4]*1000+a[5]*100+a[6]*10+a[7]<<")"<<"*"<<a[8]*10+a[9]<<"="<<900<<endl;
        }
    }while(next_permutation(a,a+10));//(6048-5973)*12=900
}
int main(){
    solve();
    return 0;
}
  • 答案

    ( 6048 − 5973 ) ∗ 12 = 900 (6048-5973)*12=900 (60485973)12=900


第五题 杨辉三角

  • 问题重现

    杨辉三角也叫帕斯卡三角,在很多数量关系中可以看到,十分重要。

    第0行: 1
    第1行: 1 1
    第2行: 1 2 1
    第3行: 1 3 3 1
    第4行: 1 4 6 4 1

    两边的元素都是1, 中间的元素是左上角的元素与右上角的元素和。

    我们约定,行号,列号都从0计数。
    所以: 第6行的第2个元素是15,第3个元素是20

    直观地看,需要开辟一个二维数组,其实一维数组也可以胜任。
    如下程序就是用一维数组“腾挪”的解法。

    // 杨辉三角的第row行,第col列 
    long long f(int row, int col){
    if(row<2) return 1;
    if(col==0) return 1;
    if(col==row) return 1;
    
    long long a[1024];
    a[0]=1;
    a[1]=1;  
    int p = 2;
    int q;
    
    while(p<=row){
    a[p] = 1;
    for( _________________ ) a[q] = a[q] + a[q-1]; //填空
    p++;
    }
    
    return a[col];
    }
    int main()
    {
    printf("%d\n", f(6,2));
    printf("%d\n", f(6,3));
    printf("%lld\n", f(40,20));  
    return 0;
    }
    

    请仔细分析源码,并完成划线部分缺少的代码。

    注意:只提交缺少的代码,不要提交已有的代码和符号。也不要提交说明性文字。

  • 解题思路

    题目要求的是第 r o w row row行第 c o l col col列的数值,所以该算法的核心思想就是用一块存储空间更新下一行。我们看第 8 8 8 9 9 9行即是填充了第一行和第二行,那么在 w h i l e while while循环中,我们发现我们所需填充的元素为 p p p个,而第 p p p列为 1 1 1,根据 f o r for for循环中的语句a[q]=a[q]+a[q-1]可知是从后面更新到前面。所以其中的条件易得。

  • 答案

q=p-1;q>0;q--

第七题 Excel地址

  • 问题重现

    Excel单元格的地址表示很有趣,它使用字母来表示列号。
    比如,A表示第1列,B表示第2列,Z表示第26列,AA表示第27列,AB表示第28列,BA表示第53列,…
    当然Excel的最大列号是有限度的,所以转换起来不难。
    如果我们想把这种表示法一般化,可以把很大的数字转换为很长的字母序列呢?
    本题目既是要求对输入的数字, 输出其对应的Excel地址表示方式。

    输入

    输入存在多组测试数据,对于每组测试数据输入一行包含一个整数
    输入的整数范围[1,2147483647]

    输出

    对于每组测试数据:输出一行表示答案

    样例输入

    26
    2054

    样例输出

    Z
    BZZ

  • 解题思路

    不难发现这实际上就是变种的进制,从低位到高位看它的权就是 2 6 位 数 26^{位数} 26,而每一位上的取值可以为 A A A~ Z Z Z,即 [ 1 , 26 ] [1,26] [1,26],这种表示方法当然可以涵盖大于 0 0 0的所有值。那么我们要做的实际上就是将十进制数转化成这种规则的数,即是不断的取位数,对 26 26 26取余同时做除法进位,存储位值。

  • 代码

/**
  *@filename:Excel地址
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-04-01 09:54
**/
#include <bits/stdc++.h>

using namespace std;

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

int pos;
vector<char> result;
void solve(){
    //不难发现,每一位上的权值为26,同时不存在0.位取值也为26。注意,这不是26进制。
    result.clear();
    while(pos>0){
        int temp1=pos%26,temp2=pos/26;
        if(temp1==0){
            result.push_back('Z');
            temp2--;
        }
        else{
            result.push_back('A'+temp1-1);
        }
        pos=temp2;
    }
    for(int i=result.size()-1;i>=0;i--){
        cout<<result[i];
    }
    cout<<endl;
}
int main(){
    while(cin>>pos){
        solve();
    }
    return 0;
}

第八题 九宫幻方

  • 问题重现

    小明最近在教邻居家的小朋友小学奥数,而最近正好讲述到了三阶幻方这个部分。
    三阶幻方指的是将1~9不重复的填入一个3*3的矩阵当中,使得每一行、每一列和每一条对角线的和都是相同的。
    三阶幻方又被称作九宫格,在小学奥数里有一句非常有名的口诀:
    “二四为肩,六八为足,左三右七,戴九履一,五居其中”,
    通过这样的一句口诀就能够非常完美的构造出一个九宫格来。
    4 9 2
    3 5 7
    8 1 6
    有意思的是,所有的三阶幻方,都可以通过这样一个九宫格进行若干镜像和旋转操作之后得到。
    现在小明准备将一个三阶幻方(不一定是上图中的那个)中的一些数抹掉,交给邻居家的小朋友来进行还原,并且希望她能够判断出究竟是不是只有一个解。
    而你呢,也被小明交付了同样的任务,但是不同的是,你需要写一个程序

    输入

    输入一个3*3的矩阵,其中为0的部分表示被小明抹去的部分。

    对于100%的数据,满足给出的矩阵至少能还原出一组可行的三阶幻方。

    输出

    如果仅能还原出一组可行的三阶幻方,则将其输出,否则输出“Too Many”(不包含引号)。

    样例输入

    0 7 2
    0 5 0
    0 3 0

    样例输出

    6 7 2
    1 5 9
    8 3 4

  • 解题思路

    还是这样的问题,和三阶幻方差不了多少,我们利用 d f s dfs dfs填充数值即可,注意剪枝,即当某一行某一列或者对角线恰好形成就开始判断是否满足题目要求的条件,这可以极大的提高效率。 需要注意的就是我们需要存储好形成的九宫格,同时也需要统计这种出现的次数,若超过一种,自然是输出"Too Many"。

  • 代码

/**
  *@filename:九宫幻方
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-04-01 10:36
**/
#include <bits/stdc++.h>

using namespace std;

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

//由于每一行每一列都是相同的,所以值为总和除以3.为15.
int a[4][4];
int result[4][4];
bool vis[10];
int cnt;
void dfs(int x,int y){
    if(x==1){
        //说明第0行已经填完了,我们剪枝判断。
        if(a[0][0]+a[0][1]+a[0][2]!=15){
            return;
        }
    }
    else if(x==2){
        if(a[1][0]+a[1][1]+a[1][2]!=15){
            return;
        }
    }
    else if(x==3){
        if(a[2][0]+a[2][1]+a[2][2]==15&&a[0][0]+a[1][0]+a[2][0]==15&&a[0][1]+a[1][1]+a[2][1]==15
        &&a[0][2]+a[1][2]+a[2][2]==15&&a[0][0]+a[1][1]+a[2][2]==15&&a[0][2]+a[1][1]+a[2][0]==15){
            for(int i=0;i<3;i++){
                for(int j=0;j<3;j++){
                    result[i][j]=a[i][j];//将结果存储。
                }
            }
            cnt++;
        }
    }
    if(a[x][y]==0){
        for(int j=1;j<10;j++){
            if(!vis[j]){
                vis[j]=true;
                a[x][y]=j;
                dfs(x+(y+1)/3,(y+1)%3);
                //还原状态。
                vis[j]=false;
                a[x][y]=0;
            }
        }
    }
    else{
        //说明这个位置不需要填,我们直接进入下一步。
        dfs(x+(y+1)/3,(y+1)%3);
    }
}
void solve(){
    dfs(0,0);//从0开始遍历查找。
    if(cnt>1){
        cout<<"Too Many"<<endl;
        return;
    }
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            cout<<result[i][j];
            j==2?cout<<endl:cout<<" ";
        }
    }
}
int main(){
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            cin>>a[i][j];
            vis[a[i][j]]=true;//标记用过了的数。
        }
    }
    solve();
    return 0;
}

第九题 拉马车

  • 问题重现

    小的时候,你玩过纸牌游戏吗?有一种叫做“拉马车”的游戏,规则很简单,却很吸引小朋友。
    其规则简述如下:假设参加游戏的小朋友是A和B,游戏开始的时候,他们得到的随机的纸牌序列如下:
    A方:[K, 8, X, K, A, 2, A, 9, 5, A]
    B方:[2, 7, K, 5, J, 5, Q, 6, K, 4]
    其中的X表示“10”,我们忽略了纸牌的花色。
    从A方开始,A、B双方轮流出牌。
    当轮到某一方出牌时,他从自己的纸牌队列的头部拿走一张,放到桌上,并且压在最上面一张纸牌上(如果有的话)。
    此例中,游戏过程:A出K,B出2,A出8,B出7,A出X,此时桌上的序列为:K,2,8,7,X
    当轮到B出牌时,他的牌K与桌上的纸牌序列中的K相同,则把包括K在内的以及两个K之间的纸牌都赢回来,放入自己牌的队尾。
    注意:为了操作方便,放入牌的顺序是与桌上的顺序相反的。
    此时,A、B双方的手里牌为:
    A方:[K, A, 2, A, 9, 5, A]
    B方:[5, J, 5, Q, 6, K, 4, K, X, 7, 8, 2, K]
    赢牌的一方继续出牌。也就是B接着出5,A出K,B出J,A出A,B出5,又赢牌了。
    5,K,J,A,5
    此时双方手里牌:
    A方:[2, A, 9, 5, A]
    B方:[Q, 6, K, 4, K, X, 7, 8, 2, K, 5, A, J, K, 5]
    注意:更多的时候赢牌的一方并不能把桌上的牌都赢走,而是拿走相同牌点及其中间的部分。
    但无论如何,都是赢牌的一方继续出牌,有的时候刚一出牌又赢了,也是允许的。
    当某一方出掉手里最后一张牌,但无法从桌面上赢取牌时,游戏立即结束。
    对于本例的初始手牌情况下,最后A会输掉,而B最后的手里牌为:9K2A62KAX58K57KJ5
    本题的任务就是已知双方初始牌序,计算游戏结束时,赢的一方手里的牌序。当游戏无法结束时,输出-1。

    输入

    输入存在多组数据,对于每组测试数据:
    输入为2行,2个串,分别表示A、B双方初始手里的牌序列。输入的串的长度不超过30

    输出

    对于每组测试数据:输出为1行,1个串,表示A先出牌,最后赢的一方手里的牌序。

    样例输入

    96J5A898QA
    6278A7Q973
    25663K6X7448
    J88A5KJXX45A

    样例输出

    2J9A7QA6Q6889977
    6KAJ458KXAX885XJ645

  • 解题思路

    模拟该游戏即可。我们发现,玩家出牌赢牌的方式就是跟队列一样, 所以我们可以用 q u e u e queue queue容器来存放它们的手牌,那么对于桌面上的牌可以用栈来存储,但有一点就是不能访问除栈顶的元素,故我采用了 v e c t o r vector vector容器来存放,这样有利于我们遍历是否存在相同的字符。这里有一个细节就是我们利用 01 01 01变量来表示游戏主场,即 0 0 0 A A A出牌, 1 1 1 B B B出牌,这样有利于减少代码量,同时也根据清楚游戏逻辑。 对于题中出现的无解,即两个人的手牌都无法为空,我们处理这种情况只需要统计游戏次数,因为手牌数是都不超过 30 30 30的,所以进行不了太久,我们取 1 e 3 1e3 1e3就足够。

  • 代码

/**
  *@filename:拉马车
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-04-01 11:29
**/
#include <bits/stdc++.h>

using namespace std;

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

queue<char> a[2];//a和b的牌序列,利用双端队列处理。
void solve(){
    //模拟游戏过程。结束的条件即有一人的牌序列为空。
    int flag=0;//0代表a出牌,1代表b出牌。我们利用异或操作转换角色。
    vector<char> temp;
    char op;
    int ans=0;//统计游戏次数,由于字符串长度是不超过30的,所以我们可以设置若游戏次数超过1e3,则游戏无法结束。
    while(!a[0].empty()&&!a[1].empty()){
        ans++;
        if(ans>=1000){
            cout<<-1<<endl;
            return;
        }
        op=a[flag].front(),a[flag].pop();
        temp.push_back(op);
        //cout<<flag<<" "<<op<<endl;
        //判断是否有相同牌点。
        int len=temp.size();
        for(int i=0;i<len-1;i++){
            if(temp[i]==op){
                for(int j=len-1;j>=i;j--){
                    a[flag].push(temp[j]);
                    temp.pop_back();
                }
                //赢牌的人再出一次牌.
                flag^=1;
                break;
            }
        }
        flag^=1;
    }
    if(!a[0].empty()){
        while(!a[0].empty()){
            cout<<a[0].front();
            a[0].pop();
        }
    }
    else{
        while(!a[1].empty()){
            cout<<a[1].front();
            a[1].pop();
        }
    }
    cout<<endl;
}
int main(){
    string temp1,temp2;
    while(cin>>temp1>>temp2){
        while(!a[0].empty()){
            a[0].pop();
        }
        while(!a[1].empty()){
            a[1].pop();
        }
        for(auto &x:temp1)a[0].push(x);
        for(auto &x:temp2)a[1].push(x);
        solve();
    }
    return 0;
}

第十题 图形排版

  • 问题重现

    小明需要在一篇文档中加入 N 张图片,其中第 i 张图片的宽度是 Wi,高度是 Hi。
    假设纸张的宽度是 M,小明使用的文档编辑工具会用以下方式对图片进行自动排版:

    1 该工具会按照图片顺序,在宽度 M 以内,将尽可能多的图片排在一行。该行的高度是行内最高的图片的高度。
    例如在 M=10 的纸张上依次打印 3x4, 2x2, 3x3 三张图片,则效果如下图所示,这一行高度为4。
    (分割线以上为列标尺,分割线以下为排版区域;数字组成的矩形为第x张图片占用的版面)

    0123456789
    ----------
    111
    111  333
    11122333
    11122333
    

    2 如果当前行剩余宽度大于0,并且小于下一张图片,则下一张图片会按比例缩放到宽度为当前行剩余宽度(高度向上取整),然后放入当前行。
    例如再放入一张4x9的图片,由于剩余宽度是2,这张图片会被压缩到2x5,再被放入第一行的末尾。此时该行高度为5:

    0123456789 
    ---------- 
            44 
    111     44 
    111  33344 
    1112233344 
    1112233344
    

    3 如果当前行剩余宽度为0,该工具会从下一行开始继续对剩余的图片进行排版,直到所有图片都处理完毕。
    此时所有行的总高度和就是这 N 张图片的排版高度。例如再放入11x1, 5x5, 3x4 的图片后,效果如下图所示,总高度为11:

    0123456789
    ----------
            44
    111     44
    111  33344
    1112233344
    1112233344
    5555555555
    66666
    66666777
    66666777
    66666777
    66666777
    

    现在由于排版高度过高,图片的先后顺序也不能改变,小明只好从 N 张图片中选择一张删除掉以降低总高度。
    他希望剩余N-1张图片按原顺序的排版高度最低,你能求出最低高度是多少么?

    输入

    第一行包含两个整数 M 和 N,分别表示纸张宽度和图片的数量。
    接下来 N 行,每行2个整数Wi, Hi,表示第 i 个图大小为 Wi*Hi。
    1<=N<=100000,1<=M, Wi, Hi<=100

    输出

    一个整数,表示在删除掉某一张图片之后,排版高度最少能是多少。

    样例输入

    样例一:
    4 3
    2 2
    2 3
    2 2
    样例二:
    2 10
    4 4
    4 3
    1 3
    4 5
    2 1
    2 3
    5 4
    5 3
    1 5
    2 4

    样例输出

    样例一:
    2
    样例二:
    17

  • 解题思路

    拿到这道题我们可能都会想到暴力去做,即枚举去掉的边,堆排计算最小高度。这样当然可行,但我们发现时间复杂度高达 O ( n 2 ) O(n^2) O(n2),明显会超时,暴力不能过完所有数据,那么我们就得另辟蹊径我们可以做一下预处理,相当于求前缀和一样,我们初始化 f f f数组,其中 f [ i ] f[i] f[i]代表第 i i i张图片到第 n n n张图片另起一行得到的新高度,这个我们怎么求呢?我们可以先求出这一行能填满多少,那么如果填满了,那么我们是不是需要另起一行再插入,假设从 j j j开始,这个时候我们发现我们可以利用我们已经求得的 f [ j ] f[j] f[j],即 f [ i ] = n o w l i n e H e i g h t m a x + f [ j ] f[i]=nowlineHeight_{max}+f[j] f[i]=nowlineHeightmax+f[j]。这个 f f f数组初始化当然简单。 那么我们怎么利用这个数组呢?即现在就可以枚举不要的图片了,我们按升序开始枚举,这样我们发现如果枚举的是 i i i,那么 i + 1 i+1 i+1 n n n的图片另起的高度我们已经知道了,而对于前面的高度,我们需要一个变量 p r e pre pre来存储,同时也需要一个行结构体来记录整行,在枚举的过程中,我们需要更新 p r e pre pre和行,如果行满了,我们就要开启一个新行,这个时候就需要更新完整行累计的高度了。理清楚这些,那么题就迎刃而解了。

  • 代码

/**
  *@filename:图形排版
  *@author: pursuit
  *@CSDNBlog:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-04-01 21:14
**/
#include <bits/stdc++.h>

using namespace std;

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

int n,m;
struct pic{
    int w,h;//图片的宽和高。
}pics[maxn];
int f[maxn];//这代表这i号图片及之后的图片另起一行插入得到的高度。
struct line{
    int w,h;//已经使用的宽度,当前的高度。
    line(){w=0,h=0;}
    line(int _w,int _h){
        w=_w,h=_h;
    }
    line operator+(pic temp){
        if(w+temp.w>m){
            //说明空间不够,我们需要对图片进行压缩。
            temp.h=ceil(1.0*temp.h*(m-w)/temp.w);
            temp.w=m-w;
        }
        return line(w+temp.w,max(h,temp.h));//更新当前的高度。
    }
    bool full(){
        return w==m;//判断空间是否已经用完了。
    } 
    void clr(){
        w=0,h=0;
    }
};
//在已有的line基础上,从第k张图开始插入,最终能得到的整体高度。
int push_till_full(line a,int k){
    for(;k<=n&&!a.full();k++){
        a=a+pics[k];
    }
    return a.h+f[k];
}
int main(){
    while(cin>>m>>n){
        memset(f,0,sizeof(f));
        for(int i=1;i<=n;i++){
            cin>>pics[i].w>>pics[i].h;
        }
        for(int i=n;i>=1;i--){
            f[i]=push_till_full(line(0,0),i);//预处理获得从第i张图片开始到第n张图片另起一行的高度。
        }
        //删除不要的图片。
        line a;//一开始没有宽度和高度。
        int ans=1e7,pre=0;//pre记录之前完整行累计的高度。
        for(int i=1;i<=n;i++){
            //不要第i张图片。
            //历史上完整行的高度是per,从i+1张图片插入得出的整体完整高度。
            ans=min(ans,pre+push_till_full(a,i+1));//用第i+1张图片填入a这个line。
            a=a+pics[i];//将第i张图片放入,作为下次迭代的line。也就是说我们每次将不要的图片加起来。
            if(a.full()){
                //行已经填满了,我们需要重置line,并且结算一个整行的高度。
                pre+=a.h;
                a.clr();//开启一个新行。
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}
posted @ 2022-03-26 16:49  unique_pursuit  阅读(89)  评论(0)    收藏  举报