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