哈尔滨理工大学21级新生程序设计竞赛(同步赛)错题笔记

目录:

  • 新生赛题目链接
    • C kiki和bob玩取石子
    • E 很二的拆分
    • F 构造字符串
    • G 信号之旅
    • H 小球滚动
    • I kiki看球赛
    • J 跳一跳
    • K Jay的小迷弟
    • L 翻转卡片

新生赛题目链接

C kiki和bob玩取石子

题目描述
kiki和bob在玩一个游戏,他们收集了一些石子,约定两人轮流从这些石子中取出一部分,但是每次只能取1、2或3颗石子,kiki和bob都非常聪明,他们总是按照最有利于自己的方式进行游戏,请你预测最后谁会赢得游戏,如果先手胜利输出kiki,否则输出bob。

输入描述
输入一个整数n(1<=n<=10^9)代表一共有多少颗石子。

输出描述
如果先手胜利输出kiki,否则输出bob。

示例一:
输入:
10
输出:
kiki

示例二:
输入:
8
输出:
bob

题解:
当时我做的时候怎么都想不明白,感觉它就是没有说怎么样算赢,之后在一个群里,大佬说广播后面说,谁先取到最后一颗石子算谁赢。啊这,那就一个一个列举,找找规律吧。

1 2 3 4 5 6 7 8
kiki kiki kiki bob kiki kiki kiki bob

我的代码如下:

#include
using namespace std;
int main()
{
    int n;
    scanf("%d",&n);
    if(n%4==0)  printf("bob");
    else    printf("kiki");
    return 0;
}

E 很二的拆分

题目描述
2022年,2月22日2时22分22秒,小二突发奇想,他认为任何一个正整数都可以拆分成若干个不同的 2 的正整数次幂,请编程帮他验证这个想法。
输入描述:
输入文件只有一行,一个正整数 (1≤ n≤ 2^31-1),代表需要判断的数。
输出描述:
如果这个正整数可以拆分成若干个不同的 2 的正整数次幂,从大到小输出这个拆分中的每一个数,相邻两个数之间用一个空格隔开。如果不存在这样的拆分,输出-1。

示例一:
输入:
7
输出:
-1

示例二:
输入:
14
输出:
8 4 2
下面是普通的代码:

#include
using namespace std;
int a[31];//存放从大到小的2的正整数次幂
int main()
{
    int n;
    scanf("%d",&n);
    if(n%2==1)//奇数直接判断
    {
        printf("-1");
        return 0;
    }
    int m = 0;
    for (int i = 31; i >= 1; i--)
    {
        if(n>=pow(2,i))
        {
            a[m]  = pow(2,i);
            n = n-pow(2,i);
            m++;
        }   
    }
    if(n!=0)//这一个是大可不必的
    {
        printf("-1");
        return 0;
    }
    for (int i = 0; i < m; i++)
    {
        printf("%d ",a[i]);
    }
    return 0;
}

或许你可能有疑问,偶数都可以写成2的正整数次幂的相加吗?如果去想想它们的二进制,那不由得说了一句:确实!
下面是二进制的写法:

#include
using namespace std;
int main()
{
    int n;
    scanf("%d",&n);
    if(n&1) printf("-1");//判断奇数
    else
    {
        for (int i = 31; i >= 1; i--)
        {
            if(n&(1<<i))    cout << (1<<i) << " ";//核心代码
        }
    }
    return 0;
}

知识点:
(1< 而n&(1<

最后再梳理一下:
通过把偶数进行二进制表示,二进制再转换为十进制的思想,不难想到只要是偶数就可以拆分成若干个不同的 2 的正整数次幂。

F 构造字符串

题目描述
给定长度为N的字符串S,要构造一个长度为N的字符串T。T初始是空字符串。S由大写字母构成。
构造过程通过反复进行以下任意操作:
从S的头部删除一个字符,添加到T的尾部
从S的尾部删除一个字符,添加到T的尾部
请你构造出字典序尽可能小的字符串T(字典序是指首先比较第一个字符,如果不同则第一个字符
较小的字符串更小,如果相同则比较第二个字符,以此类推)

输入描述:
第一行一个整数N,表示字符串的长度(1<=n<=2000)
第二行输入一个长度为N的字符串S

输出描述:
输出构造出的尽可能小的字符串T

示例1
输入:
6
ACDBCB
输出:
ABCBCD

题解:
先比较左右两端两个字符的字典序谁更小,更小的先放;如果相等,两指针都移动一位,继续比较,直到找到有大小关系的或找到尽头,最后把符合条件的放进去;其中a,b这两个变量在里面充当的角色是非常重要的,下面是我的代码。

#include
using namespace std;
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);//加速
    int n;
    string s;
    string ans;//最终要输出的字符串
    cin >> n >> s;
    int a = 0,b = n-1;
    while (a<=b)//不要忘记等号呦
    {
        bool left = false;
        for (int i = 0;a+2*i<=b; i++)
        {
            if(s[a+i]>s[b-i])   break;
            else if(s[a+i]<s[b-i])  {left = true;break;}
        }
        if(left)    ans+= s[a++];
        else    ans += s[b--];
    }
    cout << ans;
    return 0;
}

G 信号之旅

题目描述
某天,居住在VR世界的信号们收到了一条命令,命令指明每一个城市的两个信号必须要集合在一起,
信号们立刻行动了起来。VR世界是一个二维的网格,信号们只能向上下左右四个方向进行移动,并且,
由于VR世界存在一些bug,每个城市都有一个坐标是不能被走到的,也就是说信号们必须绕开这个坐标移动。
信号们希望走最短的路线到达集合位置,它们希望你能帮忙计算出需要匹配的两个信号间最短的路径长度。

输入描述:
第一行输入一个整数t(1<=t<=10^4),代表需要配对的信号对数。
接下来对于每对信号输入三行,第一行包含两个整数xA,yA,代表信号A的坐标;
第二行包含两个整数xB,yB,代表信号B的坐标;
第三行包含两个整数xP,yP,代表bug所在的坐标;
所有的x和y满足 1<=x,y<=1000
数据保证bug所在坐标与信号A、B的坐标不同

输出描述:
输出t行,第i行表示第i对信号间的最短路径长度。

示例一:
输入:
7
1 1
3 3
2 2
2 5
2 1
2 3
1000 42
1000 1
1000 1000
1 10
3 10
2 10
3 8
7 8
3 7
2 1
4 1
1 1
1 344
1 10
1 1
输出:
4
6
41
4
4
2
334
题解:
如果起点和终点的横坐标和纵坐标都不相同,那么是否绕过 bug 点对距离没有影响;
如果起点和终点的横坐标或纵坐标都相同,只需要判断 bug 点是否在由起点和终点形成的线段上,如果在,需要多 2 步将其绕开,否则没有影响。

#include
#include
using namespace std;
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n;
    cin >> n;
    while (n--)
    {
        int x1,y1,x2,y2,x3,y3;
        cin >>x1>>y1>>x2>>y2>>x3>>y3;
        int ans = abs(x1-x2)+abs(y1-y2);
        bool b1 = x1==x2&&x2==x3&&(y3>max(y1,y2)||y3<min(y1,y2));
        bool b2 = y1==y2&&y2==y3&&(x3>max(x1,x2)||x3<min(x1,x2));
        if(b1||b2)  ans += 2;//if判断条件有点长,分成2个bool来写
        cout << ans<<endl;
    }
    return 0;
}

H 小球滚动

题目描述
有n个小球以每秒1cm的速度在一个滑槽上滑行,滑槽没有封口,所以当小球滑到滑槽的端点时将会掉落下来。
滑槽的宽度只容许一个小球通过,小球之间的碰撞为弹性碰撞(即碰撞后小球的速度绝对值不变,方向相反)。
已知n个小球初始时的位置,但不知道小球初始时的朝向,小球与滑槽之间的摩擦力不计,
请你计算所有小球掉下所需要的最短和最长的时间(秒)

输入描述:
第一行输入一个整数L代表滑槽的长度(单位厘米)(1 <= L <= 10^6);
第二行输入一个整数n代表小球的个数(1 <= n <= 10^6);
第三行输入n个整数,第i个整数xi(1 <= xi <= L)代表第i个小球初始时的位置(即,距离滑槽左端xi厘米),整数之间用空格分开。

输出描述:
输出一行两个整数,分别代表所有小球掉落的最短和最长时间(秒)。

示例一:
输入:
10
3
2 6 7
输出:
4 8

思路:
复杂问题简单化:把弹性碰撞看成灵魂互换(或穿透):
最短时间就是大于中间点的小球向右滚或小于中间点的小球向左滚的最大时间;
最长时间就是小于中间点的小球向右滚或大于中间点的小球向左滚的最大时间。

评论:
首先这个ai最小是1,最大为L,则小球处在L时的最长时间为L,小球处在L时的最短时间为0。

#include
using namespace std;
typedef long long ll;
#define rep(i,l,r)  for(int i=(l);i<=(r);i++)
#define per(i,r,l)  for(int i=(r);i>=(l);i--)
const ll mod=1e9+7;
const int N = 1e6+10;
int a[N];
ll fpow(ll a,ll b){a%=mod;ll ret=1;while(b){if(b&1)ret=ret*a%mod;a=a*a%mod;b>>=1;}return ret;}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int L,n;
    cin>>L>>n;
    rep(i,0,n-1)    cin >> a[i];
    int rmin = 0,rmax = 0;
    rep(i,0,n-1)
    {
        if(a[i]>L/2)    rmin = max(L-a[i],rmin);
        else if(a[i]<=L/2)  rmin = max(a[i],rmin);
        if(a[i]>L/2)    rmax = max(a[i],rmax);
        else if(a[i]<=L/2)  rmax = max(L-a[i],rmax);
    }
    /*rep(i,0,n-1)上面的循环也可用该循环代替!
    {
        rmin = max(rmin,min(a[i],L-a[i]));
        rmax = max(rmax,max(a[i],L-a[i]));
    }*/
    cout << rmin <<" "<<rmax;
}

I kiki看球赛

题目描述
kiki看了一场非常激烈的球赛,他在比赛过程中分几次记录了当前两队的分数。
意犹未尽的kiki取出了自己记录的比分,想根据自己记录的不同时刻的比分推断
一下最多有多少次两队比分是持平的(包括0:0)。kiki希望你能帮助他计算这个结果。

输入描述:
第一行一个整数n(1<=n<=10^4),表示kyoka记录的次数。
之后输入n行,第i+i行输入以空格分开的两个整数ai,bi(1<=ai,bi<=10^9)
代表第i次记录时两队的比分(按时间顺序)

输出描述:
在第一行中输出一个整数k,代表最多有k次两队的比分是持平的。

示例一:
输入:
3
2 0
3 1
3 4
输出:
2

说明:
样例中比赛过程可能是这样的:
0:0, 1:0, 2:0, 2:1, 3:1, 3:2, 3:3, 3:4
共经历了0:0和3:3两次平分。

评论:
下面是我自己写的代码,就是想到各种情况吧,不断的讨论。因此,我认为这段代码可看但不可参考也(后面有更妙的方法)。但其中的思想却涵盖了上一局的最大值与这一局的最小值的关系的想法。

#include
using namespace std;
typedef long long ll;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,r,l) for(int i=(r);i>=(l);--i)
const ll mod=1e4+7;
ll fpow(ll a,ll b){a%=mod;ll ret=1;while(b){if(b&1)ret=ret*a%mod;a=a*a%mod;b>>=1;}return ret;}
struct Person
{
    int x1;
    int x2;
};
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n;
    cin >> n;
    struct Person p[n];
    int ans = 1;//将0:0的初始先算上
    rep(i,0,n-1)    cin >> p[i].x1 >> p[i].x2;
    ans += min(p[0].x1,p[0].x2);
    rep(i,1,n-1)    
    {
        if(p[i].x1-p[i-1].x1>0&&p[i].x2-p[i-1].x2>0&&p[i-1].x1==p[i-1].x2)//下一局比分的两个数都比上一局的大,并且上一局的比分是相等的
        {//例:1:1到3:2只有2:2一种
            if(min(p[i].x1,p[i].x2)>=max(p[i-1].x1,p[i-1].x2))  ans+=min(p[i].x1,p[i].x2)-max(p[i-1].x1,p[i-1].x2);
        }
        else if(p[i].x1-p[i-1].x1>0&&p[i].x2-p[i-1].x2>0&&p[i-1].x1!=p[i-1].x2)//下一局比分的两个数都比上一局的大,并且上一局的比分是不相等的
        {//例:1:2到3:8有2:2,3:3两种
            if(min(p[i].x1,p[i].x2)>=max(p[i-1].x1,p[i-1].x2))  ans+=min(p[i].x1,p[i].x2)-max(p[i-1].x1,p[i-1].x2)+1;
        }
        else
        {
            if(p[i].x1==p[i-1].x1&&p[i].x2==p[i-1].x2)  continue;//如1:1到1:1
            if(p[i].x1==p[i-1].x1)
            {
                if(p[i].x2>=p[i].x1)    ans+=1;//如1:1到1:2
            }
            else if(p[i].x2==p[i-1].x2)
            {
                if(p[i].x1>=p[i].x2)    ans+=1;//如1:1到2:1
            }
        }
    }
    cout << ans;
    return 0;
}

之后我参考了下大佬的代码,得到更好的写法:

#include
using namespace std;
#define rep(i,l,r)  for(int i=(l);i<=(r);i++)
#define per(i,r,l)  for(int i=(r);i>=(l);i--)
typedef long long ll;
const int N = 1e4+10;
typedef pair<int,int> pii;
const int mod = 1e9+7;
ll fpow(ll a,ll b){a%=mod;ll res =1;while(b){if(b&1)res=res*a%mod;a=a*a%mod;b>>=1;}return res;}
pii s[N];
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n;
    cin >> n;
    s[0] = {0,0};//初始化
    int ans = 0,score = 0;//ans为最终答案,score表示当前的"最低平分门槛"
    rep(i,1,n)//从1到n开始遍历
    {//如果去思考的话,会发现我们需要在上一局的最大值与这一局的最小值中找关系。
        cin >> s[i].first >> s[i].second;//输入
        int mi = max(s[i-1].first,s[i-1].second);//上一局的最大值
        int ma = min(s[i].first,s[i].second);//这一局的最小值
        mi = max(mi,score);//最低的平分标准还需要与score再来求一次最大值
        if(ma>=mi)  
        {
            ans += ma-mi+1;
            score = ma + 1;//刷新平分数(下一局需要到达的最低平分的分数)
        }
    }
    cout << ans << endl;
    return 0;
}

J 跳一跳

题目描述
LakerV最近沉迷于微信的跳一跳无法自拔。为了戒掉自己的网瘾,他决定去做算法题了。刚好,他看到一个题目叫“跳一跳”的算法题,决定点开去做。
题目的描述是这样的,有n块瓷砖铺成的直线道路上,你一开始站在第一块瓷砖上,每一块瓷砖都有一个ai权值表示当你到达这块瓷砖后,你的下一步最远能到达i+ai这个瓷砖。现在问你最大能到达第几块瓷砖。
LakerV认为这道题特别简单,于是扔给你来做,自己去做别的了。

输入描述:
输入两行
第一行是一个正整数n表示瓷砖的个数
第二行是n个正整数ai表示第i块瓷砖的权值(权值有可能为负数)
(保证所有数据范围在long long之内)

输出描述:
输出一行正整数表示你最大能到达第几块瓷砖
请注意:不能跳出瓷砖数量的范围

示例一:
输入:
5
1 1 1 1 0
输出:
5

示例二:
输入:
5
1 1 -1 0 0
输出:
3
评价:
做了好长时间,直到看了参考答案,才发现我又把题意理解错了。。。题目说的是最远能到达i+ai这个瓷砖,我一直理解成了只能到达这个距离…
题解:
一步一步找最大的步数,在i到达该最大步数前如果没有比它再大的数,则它再也不能向前了,最后再维护一下是否越界的情况,就行了。
感觉做这题需要跳出所谓的暴力情况,就是优化一下思想吧,不能一步一步按着题的要求跑来跑去的,就是这么一种很神奇又短小的方法将这题AC掉了。下面这段代码简直堪称“高屋建瓴”。

#include
using namespace std;
typedef long long ll;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
#define per(i,r,l) for(int i=(r);i>=(l);--i)
const ll mod=1e9+7;
ll fpow(ll a,ll b){a%=mod;ll ret=1;while(b){if(b&1)ret=ret*a%mod;a=a*a%mod;b>>=1;}return ret;}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll n;
    cin >> n;
    ll ans =1;//初始
    ll x;
    for(ll i = 1;i <= n;i++)
    {
        cin >> x;//由于数据范围较大,采用此方法更合适
        if(ans>=i)  ans = max(ans,i+x);  //如果它最大能到达的瓷砖比前面遍历的每个瓷砖都大,则可以继续更新。直到到了最大能到达的步数与i步数相等且不能再大时,结束。
    }
    cout << min(ans,n);//防止越界
    return 0;
}

K Jay的小迷弟

题目描述
窗外的麻雀,在电线杆上多嘴。你说这一句,很有夏天的感觉。
作为Jay的小迷弟,LakerV非常想去Jay的演唱会。
有一天,他意外地发现他买的《周杰伦的床边故事》上出现了一行神秘的字符串,并且这本书给了他一个神奇的魔法。
现在,只要他能得到最多的”Jay”的碎片数量,他就能获得Jay演唱会的门票了。但兴奋的他现在正在打包行李,无暇考虑如何得到最多的”Jay”的碎片数量,请聪明的你告诉他该怎么做。
“Jay”碎片拼凑的规则:
如果原字符串中有’J’,’a’,’y’这三个字符,那么LakerV可以用它们得到一个“Jay”的碎片。
魔法使用方法:
如果原字符串中的有一个未被使用过的且连续的“Jay”字符串,那么LakerV可以用它们对当前手上“Jay”的碎片数量*2

输入描述:
输入一行字符串(字符串长度不超过108)(字符串不包含空格)(保证所有字符均为小写或大写字母)

输出描述:
输出一行整数,表示LakerV能获得的最多的“Jay”碎片的数量

示例一:
输入:
JayyaJ
输出:
2

说明:
前面三个字符Jay有’J’,‘a’,y’三个字符,可以组成一个Jay碎片。现在LakerV手上有一个Jay碎片。后面有一个连续的Jay字符串,所以LakerV可以对手上的Jay碎片数量*2。最终LakerV手上有1×2=2个Jay碎片。

示例二:
输入:
JayJayyaJ
输出:
4

说明:
最后三个字符yaJ有’y’,‘a’,J’三个字符,可以组成一个Jay碎片。现在LakerV手上有一个Jay碎片。前面有两个连续的Jay字符串,所以LakerV可以两次对手上的Jay碎片数量*2。最终LakerV手上有1×2×2=4个Jay碎片。

示例三:
输入:
JayJ
输出:
1

说明:
前面有三个字符’J’,‘a’,'y’可以组成一个Jay碎片。最终LakerV手上有1个Jay碎片

备注:
1.魔法可以使用无限次
2.由于数可能非常大,所以需要对结果取模1e9+7
3.一开始LakerV手上”Jay”碎片的数量为0
4.未被使用过的且连续的“Jay”字符串如果用于魔法,那么该连续的“Jay”字符将不能用于得到一个“Jay”的碎片。
题解:
根据数据范围和计算方式(一个for循环),可以直接暴力。

#include
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const ll N = 1e8+10;
char ch[N];
ll fpow(ll a,ll b){a%=mod;ll res = 1;while(b){if(b&1)res=res*a%mod;a=a*a%mod;b>>=1;}return res;}//快速幂
int main()
{
    scanf("%s",ch);//用scanf读取字符串的方式!
    ll sum = 0,a = 0,b = 0,c = 0;
    for (int i = 0; i <strlen(ch); i++)
    {
        if(ch[i]=='J'&&ch[i+1]=='a'&&ch[i+2]=='y'&&i<strlen(ch)-2) sum++;//连着的Jay
        if(ch[i]=='J')   a++;
        else if(ch[i]=='a')   b++;
        else if(ch[i]=='y')   c++;
    }
    ll total = min(min(a,b),c);//Jay总数
    ll sub = total-sum;//非连着的
    if(total==0)  {printf("0");return 0;}//如果Jay为0
    else if(total==1)    {printf("1");return 0;}//如果Jay为1
    else if(sum==0) {printf("%lld",total);return 0;}//如果没有连着的Jay
    else
    {
        if(sub==0)  sub = fpow(2,sum-1);//如果Jay全是连着的,那么sub就等于0了,需要特判
        else    sub = sub*fpow(2,sum)%mod;//一般情况,别忘了再对mod取余!
    }
    printf("%lld",sub);
    return 0;
}

小知识点:

1、当使用scanf读入string时,需要这样:
string ch;
ch.resize(N);
scanf("%s",&ch[0]);
2、若要得出string类型的大小:
ch.length();
3、用char[]数组要比string快一些。

L 翻转卡片

题目描述
多多喜欢翻转卡片。他一共有N张卡片,初始时卡片被排成一行,并且全部背面朝上。
多多每次随机选择一张背面朝上的卡片进行翻转,如果翻转该卡片后存在两张相邻的卡片都是正面朝上,那么该卡片不可被翻转,多多会重新随机选择卡片。
当不存在任何一张卡片可以被翻转时,多多会停下来数一数正面朝上的卡片数量。
他想问问你:最后正面朝上的卡片数量的期望值是多少?

输入描述:
第一行输入一个整数T(1≤T≤1000),代表询问的次数。
接下来T行,每行输入一个正整数N(1≤N≤1000),代表卡片的总数

输出描述:
在这里插入图片描述
示例一:
输入:
5
1
2
3
4
5
输出:
1
1
666666673
2
500000006

说明:
哈尔滨理工大学21级新生程序设计竞赛(同步赛)错题笔记_第1张图片
这题的知识点超出我的能力范围了,晚段时间再战!

你可能感兴趣的:(错题笔记,算法,c++)