刷题笔记-24年3月第一组
Created at Updated at

2911 Words

24年-3月-第一组

P1047 ★★

校门外的树:指定轴上树的数量,指定地铁区域数目,输入区间收尾,返回剩余的数目

思路

循环遍历三遍

  • 第一遍,给所有树打上未砍伐标记0
  • 第二遍,从各个区间首位开始循环,打上已砍伐的标记1
  • 第三遍,从头顺序访问所有的树,若未砍伐,则计数+1。

总结

  • 多维数组的定义不够熟练,没有标记+遍历计数的思维;
  • 定义双重循环数组一定不要重复使用i;

P1085★★★

不高兴的津津:七天,每行(天)两个数据(校内课程时间+妈妈安排时间),每天上课时间超过八小时会不高兴,输出最不高兴的一天(排序要有稳定性)

思路1

  • 得到每天的上课总时长;
  • 用排序算法进行排序;
  • 输出排序最高的数的下标,即周几?怎么实现??

思路2

  • 使用day和max两个变量,day的初始值全部为0,若sum>8,则更改day的值为i;

总结

  • 理解上仍然不到位
    for(int i=1;i<=7;i++){
        cin>>a>>b;
        sum=a+b;
        if(sum>max&&sum>8) {
                max=sum;day=i;  //选出最不高兴的一天
        }
    }
  • 这段代码很精简的得到了最不高兴的一天。

P1089★★★

津津的储蓄计划:津津每月有零花钱三百,如果估计花完当月预算还剩有整百的钱,要判断会哪个月首次出现预算不够的情况;若没有,则计算年末津津加上利息的余额;

思路1

  • 我的思路(错解)
int main(){
    int a[12];  //存放每个月的预算
    int sum=0; //记录津津手中的钱
    int deposit=0;  //记录津津储蓄的钱
    for(int i=1;i<=12;i++){
        sum+=300;  //月初妈妈给300
        cin>>a[i];  //读入每个月的预算
        sum-=a[i];
        if(sum<0)   {cout<<"-"<<i;break;}
        if(sum>100) {sum-=100;deposit+=100;}
        if(sum>200) {sum-=200;deposit+=200;}
        if(sum>300) {sum-=300;deposit+=300;}
    }
    if(sum>0){
    sum=sum+deposit*1.2;
    cout<<sum;}
    return 0;
}

参考题解

    for(int i=1;i<=12;i++){
        sum+=300;  //月初妈妈给300
        cin>>budget;  //读入每个月的预算
        sum-=budget;
            if(sum<0)
            {
                flag=1;
                monthofdeath = i;
                break;
            }
        deposit+=sum/100;   //取整
        sum%=100;  //取余

总结

对取整和取余的效果理解不到位,这道题目的输出部分有小问题,如果出现了透支,就会结束输入。 为了得到能存的整百的张数,直接用余额对100取整:sum/100; 为了得到拿掉整百剩余的钱,直接用余额对100取余数: sum%100;

P1150★

peter的烟:设定peter初始拥有的烟的数量,设定k个烟蒂能换一根新烟,计算peter最终能吸到烟的总数。

思路1

  • 用n对k取整数:n/k,表示能用烟蒂换到的新烟的数量;
  • 用n对k取余数,n%k,表示换完烟后剩余的数目;
  • 换完一次新烟,重新计算拥有的烟,再重复上面的步骤,直到不能继续换新烟;

思路2

实际上我的做法是使用while循环:

    while(n>k)
    {
        count+=k;
        n=n-k+1;
    }
    count=count+n;
    cout<<count;

只要拥有的烟数量大于k,就抽k根烟,换1根烟;不用考虑烟蒂的问题;OJ之判定了90分,有一个点没有通过测试; 后发现有可能是忘记了等号,n=k也可以换新烟;

总结

还算简单,代码逻辑基本都是自己想出来的;

P1151★

子数整数:输入一个正整数K,找出10000到30000之间所有满足条件的五位数:该数的三个子数都能够被k整除(12345的子数为123 234 345),无解输出No;

思路

  • 通过for循环遍历区间内所有的数,遍历每个数时将其拆分成三个子数
  • 对该数的每个子数进行条件判断,若三个都满足则输出该五位数

总结

整体不难,思路没有太大问题,难点在于对整型与字符串的转换生疏了;还有划分字符串的函数;

  • 引用string库
  • 整型与字符串相互转换:
int a;
string str=to_string(a);	//整型转字符串
int a1=stoi(str);     //字符串转整型
  • 字符串的划分:
string a=str.substr(0,2);   //划分字符串;substr(pos,len)

P1152★★★

欢乐的跳:一个 n个元素的整数数组,如果数组两个连续元素之间差的绝对值包括了 [1,n-1]之间的所有整数,则称之符合“欢乐的跳”,如数组 {1,4,2,3}符合“欢乐的跳”,因为差的绝对值分别为:3,2,1。给定一个数组,你的任务是判断该数组是否符合“欢乐的跳”。

思路

  • 先要解决数组的输入;
  • 循环判断两个元素之间的差值是否为区间内元素;
  • 给区间增加一个标记,默认为0,被标记过为1;若全部被标记则认为该数组符合欢乐的跳;

60分解法

int main()
{
    int n;
    int judge=0;
    cin>>n;
    int flag[n];
    for(int i=0;i<n;i++){   //存放数组内元素的标记
        flag[i]=0;
    }
    int num[n];
    for(int i=0;i<n;i++)  //输入数组
    {
        cin>>num[i];
    }
    for(int i=0;i<n-1;i++)    //判断差值是否在数组内
    {
        int diff=abs(num[i+1]-num[i]);
        flag[diff]=1;   //在数组内,标记为1
    }
    for(int i=1;i<n;i++)
    {
        if(flag[i]==0)    //若有一个不在,则数组不是欢乐的跳
        {
            judge=1;
            break;
        }
    }
    if(judge==1)
    {
        cout<<"Not jolly";
    }
    else
    {
        cout<<"Jolly";
    }
    return 0;
}

修改|总结

  • 问题1:我的上面代码中使用了静态数组来存放num和flag,但是数组的大小n仍然是由用户决定的,如果测试点使用的n非常大,就会造成段访问错误;改用vector动态数组可以有效地避免这方面的错误;
  • 问题2:对flag的访问考虑不周,访问flag使用的数组下标是diff,而diff的值可能会超出flag数组的索引大小范围n,因此要使用diff>n来避免这种行为;
  • 修改的部分如下:
//用动态数组初始化num和flag数组:
Vector<int> flag(n,0);
Vector<int> num(n,0);
//防止访问超出flag数组的索引
if(diff>n)
	{
	cout<<"Not jolly";
	return 0;
	}

P1161★

开灯:对一排无限长的灯进行操作,对a,2xa,3xa,..txa的灯开关各按一次,求开着的那盏灯的编号

思路

  • 初始化灯的标记数组,设定值为0;
  • 取实数的整数部分,在for循环中完成对灯的操作;
  • 每对灯做一次操作,该灯的标记就+1;
  • 最后遍历灯的标记数组,找出做了奇数次操作的那个灯;

总结

通过自己的思路解出来了,先定义了初始值大小,用vector数组num存放灯的状态信息(开启或者关闭,初值为0),每次对灯做出操作后,灯的状态信息就加1,如果最后是偶数次,则灯为关闭的状态;在for循环中直接完成了对灯的所有操作;关键是用操作次数巧妙地表示出灯的状态; 技巧总结

  • 向下取整|向上取整|强制换整型|
#include<cmath>
int a=floor(1.68193); //向下取整
int a=ceil(1.120983); //向上取整
int a=static_cast<int> (1.210938); //强制转换成整型

P1179★

数字统计:给定一个指定范围,统计数字2出现的次数,每一位上的2都算;

思路

  • 遍历范围内的所有元素,整型转字符型
  • 对每一位字符型拆分成单个的元素;再进行遍历一次;

总结

又是独立解决的一道题,关键点在于合理的使用string库

  • 字符串转整型|找子串|字符串转整型:
#include<string>
string str=to_string(int i); //字符串转整型
string str1=str.substr(position,length); //找子串
int a=stoi(str); //字符串转整型

P1200★

Your ride is here: 通过一种算法判断小组名是否与彗星的名字匹配,匹配则GO,否则STAY;

思路

  • 读入小组和彗星的字符串

  • 将对应字母转换成数字

  • 算各自字母的积

  • 判断模数是否相等

总结

题目的逻辑还算简单,有几个细节问题需要注意:

  • 拆分字符串的两种方法:
#include<string>
string a="COMETQ";
char b=a.at(0);   //得到char型字符
string c=a.substr(0,a); //得到string型字符串
  • 通过ASCII码计算字母的序号
int num1='Z'-'A'+1;  //大写字母
int num2='v'-'a'+1;  //小写字母

P1304★

哥德巴赫猜想:验证区间内的所有偶数是否符合哥德巴赫猜想;

思路1 10分

关键是要拆分成两质数之和;

  • for 循环中嵌套for循环
  • 内层for循环判断是否是质数
    for(int i=4;i<=n;i++)
    {
        if(i%2==0)
        {
            for(int j=2;j<=i;j++)
            {
                if(isPrime(j)==true&&isPrime(i-j)==true)
                {
                cout<<i<<"="<<j<<"+"<<i-j<<endl;
                break;
                }
            }
        }
    }

这样做的逻辑很简单,结果应该是正确的,但是缺点特别明显,嵌套的双重for循环中一直在执行判定函数,使得时间复杂度过高,在oj中有多个测试点超时;

思路2

优化时间复杂度: 问题可能出在了质数的判断函数上: 我做的判断函数,过于啰嗦冗长:

bool isPrime(int n)   //素数的判定
{
    if(n<=1)
        return false;
    if(n==2)
        return true;
    if(n%2==0)
        return false;
    for(int i=3;i*i<=n;i+2)
    {
        if(n%i == 0)
            return false;
    }
    return true;
}

修改后:

bool isPrime(int n)   //素数的判定
{
	for(int i = 2;i * i <= n;i++)//只用枚举到sqrt(n),应该会快一点
	{
		if(n % i == 0)
			return false;
	}
	return true;
}

时间复杂度大大降低

总结

整体思路问题不大,通过不了OJ的主要原因是部分测试点超时,优化的关键是降低素数判定函数的时间复杂度,第二种算法的思路需要重点学习,优化幅度大的可能的原因是精简了很多不必要的条件判断函数。