CSP-J省一班夏令营模拟赛一补题报告

目录

一、AC情况

二、赛中概况

三、解题报告

问题一:循环递增序列(sequence)

情况: 

题意:

题解:

正解代码:

问题二:密度最高的数字(density)

情况:

题意:

题解:

正解代码:

问题三:加一(plus)

情况:

题意:

题解:

正解代码:

问题四:可达鸭棋(chess)

情况:

题意:

题解:

正解代码:

四、总结


一、AC情况

第一题赛中AC,二、三、四题赛后补题AC

二、赛中概况

先做第一题,找规律用了不少时间,做完后去看第二题, 感觉没什么思路,感觉第三题还可以,于是转头去做第三题,后来发现题目理解错误,写了一个不太完善的暴力,回头看第二题,题目理解不太到位,思路不清晰,匆忙写了一个很不完善的代码后去看了看第四题,简单枚举了几个点以偷分。

三、解题报告

问题一:循环递增序列(sequence)

情况: 

赛中AC

题意:

有一个循环递增序列:1,1,2,1,2,3,1,2,3,4…… 求该序列的第n项。

题解:

找规律,该序列有1+2+3+4+……个元素,最优做法是用n不断减去0,1,2,3……直到不能再减为止。此时的n即为答案。

正解代码:

#include<iostream>
using namespace std;
int main(){
    long long n;
    cin>>n;
    for(int i=0;i<n;i++){
        n-=i;
    }
    cout<<n;
    return 0;
}

问题二:密度最高的数字(density)

情况:

赛后补题AC

题意:

将一个数字二进制中1的个数称为该数字的密度。给定L和R,求出[L,R]区间中密度最高的数字。

比赛时想得太简单了,打了从2^1-1到2^63-1的表,最后判断输出,没有想到在[L,R]区间中不含2^n-1的数字的情况。

题解:

每次将L二进制中的一个0改成1,只要L大小不超过R,就一直改,直到不能再改为止。此时L即为答案。

注意从低位往高位改,可以使变大的值更小,从而容纳更多的1。

正解代码:

#include<cstdio>
using namespace std;
int t;
long long l,r;
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%lld%lld",&l,&r);
        long long ans=l,pos=1;  //pos就是要修改的位
        while((ans|pos)<=r){
            ans|=pos;
            pos<<=1;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

问题三:加一(plus)

情况:

赛后补题AC

题意:

给定一个数字n,对其进行m次操作,每次在其每一位上加一,求出操作后的数字有几位(答案对10^9+7取模)。

比赛时开始想错了,以为每位变成两位数时需要向前进位,实际上不需要,比如n=1912 m=1,那么就是最终n就变成了21023。而且每一次要将运算出的新数作为下一次的n。

题解:

这其实是一道二维dp的题目。

定义二维数组f,f[i][j]表示数字i经过j次修改之后的长度。

打表数字从1到9经过200000次修改之后变成多少位。

若i+j<10     说明数字i经过j次修改始终是一位数,则f[i][j]=1;

若i+j>=10   总共n次操作,经过k次操作后数字第一次产生进位,剩余n-k次操作。

正解代码:

#include<cstdio>
using namespace std;
const int MOD=1e9+7;
long long f[10][200010];
long long t,n,m;
int main(){
    //初始化
    for(int i=0;i<=9;i++){
        f[0][i]=f[i][0]=1;//数字0经过i次操作和数字i经过0次操作都始终是一位数
    }
    for(int i=0;i<=200010;i++){
        for(int j=0;j<=9;j++){
            if(i+j<10){
                f[j][i]=1;
            }
            else{
                f[j][i]=(f[0][i-(10-j)]+f[1][i-(10-j)])%MOD;
            }
        }
    }
    scanf("%lld",&t);
    while(t--){
        scanf("%lld%lld",&n,&m);
        long long ans=0;
        while(n){
            ans=(ans+f[n%10][m])%MOD;
            n/=10;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

问题四:可达鸭棋(chess)

情况:

赛后补题AC

题意:

一个n*n的棋盘,要从起点(Ax,Ay)走到终点(Bx,By)。棋盘中有魔法栅栏,在不碰到魔法栅栏并且不超出棋盘的情况下,棋子可以沿斜线走任意距离。棋子不可以越过魔法栅栏移动,也不能吃掉魔法栅栏。求最少移动多少步(步数,而非经过的格数)可以从起点走到终点。若无法走到,输出-1。

比赛时因为没时间了,所以蒙了-1和1的情况以及一组样例。

题解:

很明显使用BFS。

遍历四个方向,如果没有栅栏就将该方向点全部入队。

如果遇到栅栏,栅栏及其后所有点就无法从该方向走到,直接跳出当前循环。

但这样仍会时间超限,使用记忆化优化。

定义一个三维数组,记录该点是否被遍历过(坐标),从什么方向走过。

如果当前点被走过,判断是否从同一方向走过,如果是,返回原先记录的值,如果不是,遍历并记录。

如果当前点没被走过,同样遍历并记录。

正解代码:

#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int n,ax,ay,bx,by;
int a[2050][2050];  //记录输入的地图,栅栏是1
int step[2050][2050],f[2050][2050][4];  //step记录步数,f为记忆化数组
int dir[4][2]={{1,1},{1,-1},{-1,1},{-1,-1}};  //方向数组
void bfs(){
    queue<pair<int,int> > q;  //first记录x坐标,second记录y坐标
    q.push(make_pair(ax,ay));  //将起点入队
    step[ax][ay]=1;  //将起点步数初始化为1
    while(!q.empty()){
        int x=q.front().first,y=q.front().second;  //存储上一个点,从上一个点向四个方向分别遍历
        q.pop();
        for(int i=0;i<4;i++){  //遍历四个方向
            for(int d=1;;d++){  //枚举走的步数
                int tx=x+d*dir[i][0],ty=y+d*dir[i][1];
                if(tx<1||tx>n||ty<1||ty>n){  //走出了棋盘
                    break;
                }
                if(a[tx][ty]){  //该点是魔法栅栏
                    break;
                }
                if(f[tx][ty][i]){  //该点已经从同一方向走过了(被标记过)
                    break;
                }
                else if(step[tx][ty]){  //已经记录过该点所需步数(从另一方向走过)
                    f[tx][ty][i]=1;  //记忆化
                    continue;
                }
                step[tx][ty]=step[x][y]+1;  //该点步数等于来的点步数+1
                f[tx][ty][i]=1;  //记忆化
                q.push(make_pair(tx,ty));  //将该点入队
                if(tx==bx&&ty==by){  //走到终点了
                    return;
                }
            }
        }
    }
}
int main(){
    scanf("%d",&n);
    scanf("%d%d%d%d",&ax,&ay,&bx,&by);
    char c;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>c;  //这里用scanf不行,只能用cin
            if(c=='#'){
                a[i][j]=1;  //若该点是栅栏,就将其在a数组中标记为1
            }
        }
    }
    bfs();  //深搜
    printf("%d",step[bx][by]-1);  //由于一开始将起点的步数赋为了1,所以输出时要-1
    return 0;
}

四、总结

这次比赛总体上打的不是很好,第一题用了太长时间,第二题思路错误严重,第三题写的暴力还不完善且耗费了过长时间,前面做题太慢导致没有时间真正认真去做最后一题。应当把握好时间,认真读题分析题意,一定要细心,合理运用偷分技巧,以此取得更好的成绩。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值