11.30.)第十六届蓝桥杯模拟赛(第二期)个人题解

cbc6dcb63b95477d984081853b13aa64.png本次带来的是蓝桥杯模拟赛第二期的个人题解(笨人水平较低,大家可以在评论区指出错误/讨论更优解~)全为cpp代码

(内心os:怎么每次写blog都能发现很抽象的小问题but扣大分www 写题解不易,点点赞和关注谢谢啦~

填空题

T3 异或

c5924cd4847349e7b853ae87add5c393.png

#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    int cnt=0;
    for(int i=1;i<=2024;i++)
    {
        int tmp=2024^i;//异或
        if(tmp<2024) 
        {
           //cout<<i<<" "<<tmp<<endl;调试打印代码
            cnt++;
        }
    }
    cout<<cnt;
    return 0;
}

结果为2001

T4 最少代价(动态规划)

b7ac36280b5b40b9b6d3c9479ce8011c.png

从贪心的角度来看,一个大于20的数肯定是花费10收益最高,所以开始想反推着做…(应该也没问题?)但是想到19花费3就会变成28,再花费3就会变成36,似乎很多数不确定性太多,感觉有点顾不过来(正着模拟应该也能做出来,但当时我选择了写个通解…)

max_digit函数没什么好说,数位最大的那个嘛。dp[i]表示整数从1变为i,最少需要多少代价

感谢评论区指正!这里dp数组更新数据应该是由原数更新下一个数,详见代码

下面看代码

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

int max_digit(int n)
{
    int res=0;
    while(n)
    {
        res=max(res,n%10);
        n/=10;
    }
    return res;
}//分离数位,返回最大值
int dp[5000];
int main()
{
    memset(dp,0x3f,sizeof dp);
    dp[1]=0;//初始条件
    for(int i=1;i<=2050;i++)
    {
        dp[i+1]=min(dp[i+1],dp[i]+1);
        dp[i+max_digit(i)]=min(dp[i+max_digit(i)],dp[i]+3);
        dp[2*i]=min(dp[2*i],dp[i]+10);
    }
    //for(int i=2;i<924;i++) printf("dp[%d]=%d\n",i,dp[i]);//调试打印代码
    cout<<dp[2024];

    return 0;
}

结果为79

*T5 和的最大(动态规划)(12.9.更新正解!!)

感谢评论区指出错误以及提供的朴素dp算法~(编程题的话纯暴力会超时,但是这毕竟是填空题,有兴趣可以转至评论区看朴素做法)

博主下面更新了一种时间复杂度仅仅O(mn)的算法~~

38cbaace883e4d389802630e3b6cb479.png

这题显然纯暴力是不可取的(由组合数的公式可知会有2的100次方种组合,指数爆炸是很恐怖的),正解应该是动态规划,已更新正确代码.

大致思路就是用dp数组存储很多数之和%24后余数为0~23的最大和。二重循环:外层循环相当于每次尝试加入数组中一个新数,内层循环先计算dp[j] 加完这个新数后%24的余数t,再看是否可以更新dp[t]. 根据题意,所有数都尝试加过后,输出dp[0](dp[0]表示很多数之和%24==0时,这很多数的最大和)即为答案

下为AC Code, 时间复杂度 O(mn)   其中m为取余因子(),n为数组元素个数

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int mod=24;

int a[]={534,386,319,692,169,338,521,713,640,692,969,362,311,349,\
308,357,515,140,591,216,57,252,575,630,95,274,328,614,18,605,17,980,\
166,112,997,37,584,64,442,495,821,459,453,597,187,734,827,950,679,78,\
769,661,452,983,356,217,394,342,697,878,475,250,468,33,966,742,436,343,\
255,944,588,734,540,508,779,881,153,928,764,703,459,840,949,500,648,163,\
547,780,749,132,546,199,701,448,265,263,87,45,828,634};
int dp[mod]; //dp[i]表示部分数字的和 %24等于i,这个和的最大值,所求即为dp[0]
int backup[mod];//备份数组防止串联更新

int main()
{
    for(int i=0; i<100; i++)
    {
        memcpy(backup, dp, sizeof dp);
        for(int j=0; j<mod; j++)
        {
            int t=(backup[j]+a[i])%mod;
            dp[t]=max(dp[t], backup[j]+a[i]);
        }
    }
    cout<<dp[0];
    return 0;
}

结果为49176

编程题

T8 子字符串

开始没认真读题…写完了后来才发现子串是LANQIAO就可以TwT,遂重写

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;

char c[1010];
int main()
{
    char str[]="LANQIAO";
    scanf("%s",c);
    int len=strlen(c);
    int cnt=0;
    for(int i=0;i<len;i++)
    {
        if(c[i]==str[cnt])
        {
            cnt++;
        }
        if(cnt==7)
        {
            printf("YES");
            return 0;
        }
    }
    printf("NO");
    return 0;
}

T9 口字形

ab5682e30430456ca33a6339036bab51.png

显然纯暴力写法最多能过30%样例…博主用二维前缀和处理后算了下时间复杂度,应该还是会有部分超时(?),只能说差强人意罢。special函数中其实不需要特判口字形中间只有唯一格子的情况,刚画了下图是一样滴!(只是考试时不想再考虑&改…)

主函数中的i<=n-2,j<=m-2根据图很容易得出结论。题目要求x1<x2, y1<y2,我们枚举的点都是左上角的点,然后最内层用k来控制右下角点的位置(所以才有k<=n-i&&k<=m-j,防止越界,你如果真拿不准可以试下边界情况嗷)。看代码(就是二维前缀和之差)

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N=310;

int n,m;
int a[N][N],s[N][N];

//计算含内部点的矩阵区域值
int caculate(int x1,int y1,int x2,int y2)
{
    return s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1];
}
//计算口字形区域值
int special(int x1,int y1,int x2,int y2)
{
    if(x1+1==x2-1 && y1+1==y2-1) return caculate(x1,y1,x2,y2) - a[x1+1][y1+1];
    return caculate(x1,y1,x2,y2) - caculate(x1+1,y1+1,x2-1,y2-1);
}

int main()
{
    scanf("%d%d",&n,&m);
    //预处理前缀和
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            scanf("%d",&a[i][j]);
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
        }
    
    int res=-1e9;
    for(int i=1;i<=n-2;i++)
    {
        for(int j=1;j<=m-2;j++)
        {
            for(int k=2;k<=n-i && k<=m-j;k++)
            {
                res=max(res, special(i,j,i+k,j+k));
            }
        }
    }
    cout<<res;
    return 0;
}

T10 寻宝游戏(大模拟)

9da2e5b65e92439fa070ab4c0657d698.png

(刚写博客时发现x=y=dir=0的恢复没有写!!!!WA的一声就哭出来了(bushi

看到题第一反应是大模拟,再看了一眼数据范围,最后有到1e6!(当时就知道糟了这题肯定有很巧妙的解法并且我想不出来)挣扎了一下还是决定写个大模拟拿到那大半的分就好……简单试了下样例就开始写大模拟了

于是dx、dy分别对应向北、东、南、西走(我是按顺时针写的方向数组,其实按逆时针也可以,但一定要有顺序,对后面dir改变有用),caculate_position函数里dir的改变稍微需要一点设计(这里最好在纸上画几个情况),方向数组的有序使得设计L\R改变朝向的程序很简单:dir=(dir+3)%4

*在调整完朝向后向这个方位前进一格即可~

simulate函数写的很冗余,但是情急之下这样凑一下也未尝不可qwq(这还是用caculate函数减少了大量重复代码的情况下)。tmp数组就是拷贝原字符串str后,修改其中一个字符,然后传参进行模拟(这保证了原数据不变)

在每次模拟后补药忘记恢复x=y=dir=0啊啊啊啊(虽然不至于爆0但我感觉也差不多了呜呜呜呜

这里稍微考虑设计了下存储方式,用vector的pair存最终抵达的坐标,然后用points.erase(unique(points.begin(),points.end()),points.end());去重操作后输出points所含元素个数(这样可以避免额外很多操作去比对、去重)

下面是赛后微调代码

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cstring>

using namespace std;

const int N=100010;

typedef pair<int,int> PII;

int dx[4]={-1,0,1,0}, dy[4]={0,1,0,-1};//按顺时针对应的方向数组

vector<PII> points;
char str[N], tmp[N];
int n;

//计算移动后朝向dir 以及坐标
void caculate_position(int &x,int &y,int &dir, char c)
{
    if(c=='L') dir=(dir+3)%4;
    if(c=='R') dir=(dir+1)%4;
    x+=dx[dir];
    y+=dy[dir];
}
//模拟单次
void caculate(int &x,int &y,int &dir,char c[])
{
    for(int j=0;j<n;j++)
    {
        caculate_position(x,y,dir,c[j]);
    }
    points.push_back({x,y});//将可以抵达的点记录
}
void simulate()
{
    for(int i=0;i<n;i++)
    {
        strcpy(tmp,str);//拷贝
        int x=0, y=0, dir=0;
        if(tmp[i]=='F')
        {
            tmp[i]='L';
            caculate(x,y,dir,tmp);
            x=y=dir=0;//!恢复
            tmp[i]='R';
            caculate(x,y,dir,tmp);
        }
        else if(tmp[i]=='L')
        {
            tmp[i]='F';
            caculate(x,y,dir,tmp);
            x=y=dir=0;//!!恢复恢复
            tmp[i]='R';
            caculate(x,y,dir,tmp);
        }
        else if(tmp[i]=='R')
        {
            tmp[i]='L';
            caculate(x,y,dir,tmp);
            x=y=dir=0;//!!!恢复恢复恢复
            tmp[i]='F';
            caculate(x,y,dir,tmp);
        }
    }
}

int main()
{
    scanf("%d",&n);
    scanf("%s",str);
    simulate();
    sort(points.begin(),points.end());//排序+去重
    points.erase(unique(points.begin(),points.end()),points.end());
    int ans=points.size();//剩余元素个数
    cout<<ans;
    return 0;
}

未完待续……

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值