2021年广东工业大学第11届腾讯杯新生程序设计竞赛(同步赛)错题笔记

目录:

  • 题目链接
    • A 比比谁更大
    • B 过生日
    • D 机器人
    • G 拼牛牛
    • I 史莱姆
    • J 水题
    • K 烧烤丝瓜
    • L 歪脖子树下的灯

题目链接

A 比比谁更大

题目描述
在一个夜黑风高的晚上,牛哥哥吃完心爱的烤串串之后,独自一人漫步在南亭的路上。由于吃的太饱了,牛哥哥决定想一道新生赛的题考(折)验(磨)一下新生,通过脑部的思想风暴促进肚子的消化。
突然间,有两块石头碰瓷了牛哥哥的脚,他气急败坏地捡起来,想要让它们消失在远方。但牛哥哥灵机一动,想要用这两块石头出道题目,以此来显示自己思维之强大。牛哥哥很喜欢竞赛,因为可以一直与别人竞争,因此他也想让两块石头一决高下!
假设两块石头的重量分别为a、b,如果只是简单地比较两者的重量,未免也太简单了。因此,牛哥哥决定计算a!和b!的大小,但因为一个数的阶乘十分之巨大,牛哥哥决定将他们阶乘的结果模一个数再进行比较。此时牛哥哥心中浮现出一个奇妙的模数------999068070,这可是一个很巧妙的模数哦,他心里想。
至此牛哥哥已经完善了他的比较过程,即已知a,b,比较(a!)%999068070和(b!)%999068070的大小,大者则胜出,如相等则打平
作为新手的你们,需要接受牛哥哥的考验,完成比较。

输入描述:
第一行输入两个两个正整数a,b(1≤a,b≤109).

输出描述:
如果重量为a的石头胜出,则输出"a is the winner!"
如果重量为b的石头胜出,则输出"b is the winner!"
如果两者打平,则输出"There is no winner!"
注意:不需要输出""

示例一:
输入:
2 1
输出:
a is the winner!

示例二:
输入:
1 2
输出 :
b is the winner!

示例三:
输入:
2 2
输出:
There is no winner!
示例四:
输入:
23 22
输出:
b is the winner!

备注:
N的阶乘(记作 N!)是指从1到N(包括1和N)的所有整数的乘积。 取余运算符 (%),一个表达式的值除以另一个表达式的值,返回求余结果。求余是一种数学计算方法,指一个数除以另一个数,不够除的部分就是余数,就是求余的结果。
例如:5%3=2
(22!)%999068070=785674890
(23!)%999068070=87297210

评价:
要不是看了答案解析,我是万万想不到会是这么个情况啊!虽然我后面想到了这个模有奥秘之处,但我也想不到这个啊:99068070=2×3×5×7×11×13×17×19×103。
因此当n大于等于103时,答案为0;n小于103时,暴力计算即可。
下面是代码(我不服!):

#include
using namespace std;
typedef long long ll;
const int mod = 999068070;
int f(ll n)
{
    if(n<=1)    return 1;
    else return n*f(n-1)%mod;
}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll a,b;
    cin >> a >> b;
    if(a>=103&&b>=103) {cout << "There is no winner!";return 0;}
    else if(a>=103&&b<103)  {cout << "b is the winner!";return 0;}
    else if(a<103&&b>=103)  {cout << "a is the winner!";return 0;}
    else
    {
        ll x = f(a);
        ll y = f(b);
        if(x>y) {cout << "a is the winner!";return 0;}
        else if(x<y)    {cout << "b is the winner!";return 0;}
        else    {cout << "There is no winner!";return 0;}
    }
    return 0;
}

不服?那咱再来讲讲:

合数分解:
由数学基本定理可知:任何一个大于1的非素数整数(即合数)都可以唯一分解成若干个素数的乘积。

由此,则可以判定一定存在某个数(或某个位置吧),使得之后的阶乘对mod取模都为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 = 999068070;

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll a,b;
    cin >> a >> b;
    auto solve = [](int x)
    {
        if(x>=200)  return 0ll;
        ll r = 1;
        rep(i,1,x)  r = r*i%mod;
        return r;
    };
    ll x = solve(a),y = solve(b);
    if(x>y) cout << "a is the winner!";
    else if(x<y)    cout << "b is the winner!";
    else    cout << "There is no winner!";
    return 0;
}

还可以判断结果是否为0,如果为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 = 999068070;

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll a,b;
    cin >> a >> b;
    auto solve = [](int x)
    {
        // if(x>=200)  return 0ll;
        ll r = 1;
        rep(i,1,x)
        {
            if(r)
            {
                r = r*i%mod;
                continue;
            }
            break;
        }
        return r;
    };
    ll x = solve(a),y = solve(b);
    if(x>y) cout << "a is the winner!";
    else if(x<y)    cout << "b is the winner!";
    else    cout << "There is no winner!";
    return 0;
}

知识点:

  1. 合数的性质
  2. 匿名函数的基本使用
  3. #define的妙用

B 过生日

题目描述
今天是Bob的生日,Alice给Bob准备了精美的礼物,但是并不想让Bob轻易的拿到这份礼物,了解到Bob最近在学习字符串,于是Alice给Bob出了一道字符串题目,打算考考他。
Alice在黑板上快速的写出了一个字符串和一个数字k,Alice想让Bob找到是否有这样一个区间,使得其中的一个字符的个数大于等于k。告诉Alice是否可以找到符合条件区间。如果可以找到请告诉Alice最短的区间长度。
正在过生日的Bob显然并不想做题但是又想拿到Alice的礼物,于是找到了你帮助他。请问你是否可以写一个程序帮助Bob。

输入描述:
第一行会给定一个t代表样例的个数(1≤t≤10),对于每组样例会有两行输入,第一行会给出一个字符串s,第二行会给出一个k。(1≤|s|≤105,1≤k≤|s|) (|s|代表字符串的长度)

输出描述: 对于每一个样例,请输出一行,如果能找到请输出最小的区间的长度,否则输出-1。

示例一:
输入:
4
aba
2
abc
2
abczgbnhe
2
abczgbnaegsca
3
输出:
3
-1
5
13

评价:
如果是遍历字符串一个一个比较是否相等两个for循环什么的话(非常暴力),那么一定会超时。毕竟数据在那摆着(但我还是尝试了…),但是有一种做法(似暴非暴),利用vector数组的形式,下面是代码:

#include
using namespace std;
typedef long long ll;
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        string s;
        int k;
        cin >> s>>k;
        vector<int>v[26];//定义“二维数组”
        int number[26] = {0};//初始化(将26个英文字母映射成0~25的数字)
        int res = INT_MAX;//先初始化
        for (int i = 0; i < s.size(); i++)
        {
            number[s[i]-'a']++;//遍历字符串,每个字符出现一次则在其映射的数字上加1。
            v[s[i]-'a'].push_back(i);//将同一个字符的下标都存在对应映射的数字下的数组中。(类似二维数组的思想,但是可以不同列数)
        }
        for (int i = 0; i < 26; i++)//遍历映射的每个字母
        {
            if(number[i]>=k)//如果该字母的最大出现次数大于等于k
            {
                for (int w = k-1; w < v[i].size(); w++)//从同一字符的下标开始遍历
                {
                    res = min(res,v[i][w]-v[i][w-k+1]+1);//每k的区间的下标数相减,求出最小区间
                }
            }
        }
        if(res==INT_MAX)    cout << "-1"<<endl;//如果没有更新过res,则不存在。
        else    cout << res <<endl;
    }
    return 0;
}

知识点:
vector数组(优化暴力)的思想

D 机器人

题目背景:
ZYL最近迷上了机器人,看着机器人在赛道上跑来跑去,ZYL陷入了思考…
题目描述:
现在,操场上有n×n个机器人组成的方阵,每个机器人都有自己独一无二的编号i(1≤i≤n×n)。
现在,机器人教官想让他们排整齐,即第i行第j列的机器人编号恰好等于(i−1)×n+j,但教官只能发出一种指令。
指令的内容:
让方阵最外周的机器人顺时针走动一位。
例如(教官喊了一次指令):
2021年广东工业大学第11届腾讯杯新生程序设计竞赛(同步赛)错题笔记_第1张图片
输入描述:
输入的第一行包含一个正整数n(1≤n≤10)------代表这是一个n×n的方阵
接下来n行,每行包含n个整数a_i,j(1≤a_i,j ≤n×n)------代表原方阵中第i行第j列的机器人的编号

输出描述:
输出共一行,YES或NO.

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

示例二:
输入:
2
1 2
4 3
输出:
NO
评价:
做这题思考了好长时间,算是深刻理解了一下题意吧:内圈的数字一定是已经符合题意的顺序,不然教官让外围一直转永远也不可能“整齐”,因此不用再去考虑内圈了。因此这到题的意思就是外围无限次的顺时针移动,是否有一次可以“整齐”,若可以,则YES,否则NO。我采用的是直接的做法:只在原数组中进行移动,其中有4个临界需要判断,这个临界挺有意思的。可以在循环过程中打印出移动后的矩阵数字,看看是否为顺时针的正确移动,(我是以三阶方阵算的),最后三阶成功打印,说明临界问题处理成功,最后运行就通过了。
下面是我的代码:

#include
using namespace std;
const int N = 15;
int a[N][N];
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n;
    cin >> n;
    for(int i = 1;i <= n;i++)//输入数据
        for (int j = 1; j <= n; j++)
            cin >> a[i][j];
    for(int k = 1;k <= n*n;k++)
    {//本次转换,只在一个数组中进行,因此需要提前用变量存储下临界。
        bool flag = true;//每次进入循环,重置下flag
        int t1 = a[1][1];
        int t2 = a[n][1];
        int t3 = a[n][n];
        int t4 = a[1][n];
        for (int i = 2; i <= n; i++)
        {
            if(i==n)    a[1][2] = t1;//临界
            else{a[1][n-i+2] = a[1][n-i+1];}//上面一行少a[1][1],从右边开始算
            if(i==n)    a[n][i-1] = t3;//临界
            else    a[n][i-1] = a[n][i];//下面一行少a[n][n],从左边算
        }   
        for (int i = 2; i <= n; i++)
        {
            if(i==n)    a[i-1][1] = t2;//临界
            else    a[i-1][1] = a[i][1];//左边少a[n][1],从上面算
            if(i==n)    a[n-i+2][n] = t4;//临界
            else    a[n-i+2][n] = a[n-i+1][n];//右边少a[1][n],从下面开始算
        }
        for (int i = 1; i <= n; i++)
        {
            for (int j = 1; j <= n; j++)
            {
                if(a[i][j]!=(i-1)*n+j)//判断
                {
                    flag = false;
                    break;
                }
            }
            if(!flag)   break;
        }
        if(flag)    {cout << "YES";return 0;}
    }
    cout << "NO";
    return 0;
}
/*测试数据
3
1 2 3
4 5 6
7 8 9
*/

G 拼牛牛

题目描述
真牛币提现活动的规则是这样的:初始每个人都会获得一个免费的红包,红包的金额是n(1≤n≤100且为整数),然后你可以召集你的好友帮你提高红包的额度,每一个好友都能够增加一定的额度,当红包的额度到达或超过100时,你就可以获得100真牛币了。
然而,一旁的壕哥眼神犀利了起来,他发现,第一个好友增加的额度一定是1元,随后好友增加的额度将是上一个好友增加额度的k倍(0 但是牛哥一点也不慌,他可是一国之君啊,他决定昭告全体牛牛,让所有人都帮他拼一刀。现在,请你告诉牛哥,如果他发动全体1145141919810个牛民,最终牛哥究竟能否成功提现呢?

输入描述:
第一行输入一个正整数T(1≤T≤100)表示样例数量
接下来T行每一行输入两个数字,一个整型数字n(1≤n≤100且为整数)和一个浮点数k(0

输出描述:
每组样例每行一个输出,所有输出不包含双引号
对于每组样例如果牛哥能够提现成功,输出"YES",否则输出"NO"

示例一:
输入:
3
99 0.1
98 0.5
98 0.6
输出:
YES
NO
YES
评价:
当时这道题我没做到。。。之后想到了等比数列直接求出,也可以利用等比数列前n项和的极限公式求。下面是代码:

#include
using namespace std;
typedef long long ll;
ll fpow(ll a,ll b){ll ret=1;while(b){if(b&1)ret=ret*a;a=a*a;b>>=1;}return ret;}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n;
    cin >> n;
    while (n--)
    {
        int x;
        double k;
        cin >> x>>k;
        double res = x+(1-fpow(k,1145141919810))/(1-k);//也可以写成res = x+1/(1-k),这是等比数列前n项的极限公式
        if(res-100>=1e-6)    cout << "YES"<<endl;//这个挺有意思的,浮点数的比较
        else    cout << "NO" << endl;
    }
}

I 史莱姆

题目描述
讨伐魔王的路上总是充满阻碍,作为魔王的下属,为了更好的辅佐魔王,史莱姆里曼想知道自己分裂能力的最大分裂数量,已知里曼的生命值为n,里曼每次分裂可以变成两个生命值分别为a,b的自己,a,b均不为1的正整数且满足a×b=n,分裂后的史莱姆可以继续分裂,求经过最多次分裂后史莱姆的数量。

输入描述:
第一行:一个整数n。
数据满足:1≤n≤95718.

输出描述:
共一行:一个数字,表示里曼的最多分裂数量。

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

评价:
这道题我感觉我已经研究的非常透彻了,光题意我理解出了多种意思,最终才终于明白:一个分裂成两个的话,原有的一个可以理解成不存在了,就像细胞分裂似的,所以,当只有1个或素数个史莱姆的时候,最多次分裂(那也不能再分裂了),分裂后的数量就是它自己!
做错分析:
我对这题的题意理解不到位,总的来说,还是没理解好题意,做法方面还是可以的。

下面是我的正确代码:

#include
using namespace std;
typedef long long ll;
int n;
int res = INT_MIN;//先定义出int的最小值
bool prime(int n)//判断素数
{
    if(n<2) return false;
    for (int i = 2; i <= sqrt(n); i++)
    {
        if(n%i==0)  return false;
    }
    return true;
}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin >> n;
    int num = 0;
    if(n==1)    {cout <<1;return 0;}//特判n=1的时候
    if(prime(n))    {cout << 1;return 0;}//如果是素数,则分裂后是1.
    for (int i = 2; i <= 95718; i++)
    {
        if(n%i==0)  
        {
            while (n%i==0)
            {
                num++;
                n = n/i;
            }
            if(n==1)    break;
        }
    }
    cout << num;
    return 0;
}

J 水题

题目描述
众所周知,广工的宿舍没有电梯,送水的叔叔阿姨每天都需要辛苦的爬楼梯送水。
所幸的是叔叔阿姨们都配备了电动爬楼机,工作显得轻松许多。
但是广工的人太多了,即使是用机器送水还是显得不够快。
所以叔叔阿姨想让你帮忙计算一下送完n+1层楼,所需要的最少时间。
已知宿舍的楼层数为n+1,电动爬楼机一次最多可以运k桶水,叔叔用三轮车把所有需要送的水事先送到1楼(所以1楼的水就算已经送完了,不再考虑),而第i+1层需要送a_i桶水。机器和每桶水的重量皆为1,机器每次上下楼时,每1点重量就需要花费1个单位的时间,例如机器携带3桶水上一层楼需要花费4个单位时间。而且在机器上装上或卸下1桶水也需要花费1个单位的时间,假定叔叔到达某个楼层卸下水就算送达,请你计算送完所有水并且回到1楼所需要的最少时间。

输入描述:
第一行两个整数,第一个整数n代表有n+1楼,(1≤n≤1000000),第二个整数k代表爬楼机最大装载量为k桶(1≤k≤1000000000)。接下来一行有n个整数a,用空格隔开,a_i代表第i+1层楼需要送的水的数量,(0≤a_i≤10000)。

输出描述:
输出一个整数,代表送完所有水所需要的最少时间。

示例一:
输入:
3 3
1 2 3
输出:
36
样例1解释
叔叔可以装3桶水,到2楼放下1桶,到3楼放下两桶,然后返回至1楼,再运三桶水送到4楼卸下返回至1楼 整个过程所花费的时间为:
3(装水) + 4(上2楼) + 1(卸1桶) + 3(上3楼) + 2(卸2桶) + 2(从3楼回到1楼) + 3(装水) + 3*4(从1楼运到4楼) + 3(卸水) + 3(从4楼回到1楼) = 36
也可以先送4楼的水,再送2楼跟3楼。

示例二:
输入:
4 4
1 2 3 4
输出:
68

评价:
其实这个题对我来说,还真挺难的。我也是看了别人的代码并尝试着去模仿着写,再用测试用例去分析步骤,最后才渐渐理解的。“水题”不水呀!
在这里插入图片描述

题解:
首先要从这道看似复杂的题中找到"变量"与"不变量",先将不变量计算出来,再思考贪心的处理。其中的思想挺强的,还需再多思考思考。
下面是代码:

#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 ll N = 1e6+10;
ll a[N];

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll n,k;
    cin >> n>>k;
    ll ans = 0;//最终结果
    rep(i,1,n)  {cin >> a[i];ans+=a[i]*(i+2);}//一边输入数据,一边计算不变量:水的装、卸、搬运
    ll sum = 0;
    per(i,n,1)//贪心思想,优先满足最高层,下面就是计算爬楼机的"上与下"
    {//该循环应结合示例一去模拟步骤,才能理解其中的奥秘(其中个别公式作用很大)
        sum+=a[i];
        if(sum>0)
        {
            ans+=sum/k*i*2;//  sum/k为要送多少次,i为楼的距离,乘2为计算上下楼
            sum%=k;//看还剩多少
            if(sum>0)
            {
                sum-=k;
                ans+=i*2;//只能送一次,楼梯距离乘上下楼
            }
        }
    }
    cout << ans << endl;
    return 0;
}

K 烧烤丝瓜

题目描述
三哥很喜欢吃丝瓜,尤其是烤丝瓜,三哥有一个巨大丝瓜想要烤着吃。为了节约能源,厨房里的炉子在打开k分钟后自动关闭。烹饪时,三哥每隔d分钟就去厨房,如果炉子关了,她就打开炉子。当炉子关闭时,它依然保持温度,但会有所降低。众所周知,如果打开炉子,丝瓜在炉子上烹调到熟需要t分钟,如果关闭炉子,炉子依然是温暖的,则需要2t分钟。你需要弄清楚,三哥需要花多少时间来烤丝瓜。(当炉子关闭的瞬间三哥进入厨房他也会打开炉子)
输入描述:
单行包含三个整数k,d,t(0<k,d,t<10^18).

输出描述:
打印单个数字,烹饪总时间(分钟)。如果你的答案与正确答案的差的绝对值小于10^−8则认为正确

示例一:
输入:
3 2 6
输出:
6.500000000

示例二:
输入:
203954 12360 842
输出:
842.000000000
题解:
以一个周期为标准进行计算,我感觉我写的挺好理解的,但是跟人家对比起来,我的代码又有点显长,下面是我的代码:

#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 ll 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;}

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    ll k,d,t;
    cin >> k >> d >> t;
    ll T;//周期
    if(k<=d) T = d;//求周期
    else    {if(k%d==0)  T = k;else    T = (k/d+1)*d;}
        double temp;
        temp = k+0.5*(T-k);//一个周期的量
        if(temp<=t)
        {
            int x = 1.0*t/temp;
            temp = x*temp;
            if(k>=t-temp)    cout << fixed << setprecision(9) << (1.0*T*x+t-temp);
            else if(k<t-temp)   cout << fixed << setprecision(9) << (1.0*T*x+k+2*(t-temp-k));
        }
        else
        {
            if(k>=t)   cout << fixed << setprecision(9) << (1.0*t);
            else  cout << fixed << setprecision(9) << (1.0*k+2*(t-k));
        }   
    return 0;
}

L 歪脖子树下的灯

题目描述
11月份不知是深秋还是早冬,寒风翻过窗户与我相会。阵阵的凉意却让我还挺舒服。楼下庭院里的树早已脱去了叶子,光秃秃的枝干张牙舞爪的伸着。在风中摇曳着一棵歪脖子树。树下挂着一盏年久失修的灯。灯是那种老式的灯,开关自然也是拉线开关。透过窗户,看着那泛黄的麻线随风飘荡,我却冒出了个奇怪的想法:假设灯的初始状态是暗的,我每拉动一次开关,灯就会有p的概率转换状态(亮->暗 或 暗->亮)。那么当我拉动n次之后,灯是亮着的概率是多少呢?

输入描述:
第一行一个t(1≤t≤100),代表测试数据组数。
对于每一个测试样例, 第一行有一个整数n(1≤n≤100),和一个实数p(0≤p≤1), 分别代表拉动开关的次数和灯转换状态的概率p

输出描述:
对于每一个测试样例,输出一个P,代表灯是亮着的概率,如果你的答案与正确答案的差的绝对值小于10^−4则认为正确

示例一:
输入:
2
1 0.5
2 0.6
输出:
0.500000
0.480000

评价:
当时我想直接用公式C多少多少p的多少次方(1-p)的多少次方直接解决,但是如果是C100,1的话这个利用公式就得出现100的阶乘,且这道题也没说模多少,所以这种方法一定会爆掉。当时我就被提示出现了:浮点错误的问题。这种错误挺难找我为啥错的,之后才慢慢发现是爆掉才出现的浮点错误问题,如果当时判断的快的话,很有可能立即换一个方法,但是由于没有发现的缘故,又浪费的大量时间。
废话不多说,上代码:(挺好理解的代码呦!)

#include
using namespace std;
double on[105],off[105];//double类型的
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int t;
    cin >> t;
    on[0] = 0;//初始化
    off[0] = 1;//初始化
    while(t--)
    {
        int x;
        double p;
        cin >> x >> p;
        for (int i = 1; i <= x; i++)
        {
            on[i] = on[i-1]*(1-p)+off[i-1]*p;//如果这次是亮灯,那么上一次没亮灯的乘以这次开关转换成功的概率,上次亮灯的乘以这次转换不成功的概率。
            off[i] = off[i-1]*(1-p)+on[i-1]*p;//类推。
        }
        cout << fixed << setprecision(6) << on[x] << endl;
    }
    return 0;
}

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