广州大学2024年ACM校赛预选赛II复盘

本文分享了作者在广州大学ACM校赛预选赛中的经验,包括代码优化技巧、数学逻辑在算法中的运用以及重要性,强调编写测试样例和处理特殊情况的重要性。
摘要由CSDN通过智能技术生成

markdown手写笔记 && 2024年3月19日广州大学ACM校赛预选赛复盘

经验一:优化尽可能写。

  因为可能代码中就是因为某种特殊情况而导致算法超时,你要是考虑到了这种特殊情况,对代码进行优化,就能提高算法效率。
例如:在兔子分萝卜题目中:
  题目描述:https://vjudge.net/contest/616248#problem/E
  优化前的代码:

#include<iostream>
using namespace std;
int main()
{
	int n, m, k;
	cin >> n >> m >> k;

	int a = 1;  // a波及左右两边的兔子个数,给喜爱兔子的萝卜数量
	int c = 1;  // 每次给多一个萝卜给最喜爱兔子时,需要消耗给多个兔子的萝卜数量

	m -= n;  // 预分配每个兔子一个萝卜,剩余萝卜数量m

	while (m > 0)
	{
		if (k + a <= n)  // 判断当前波及范围右边是否有兔子
			c++;
		if (k - a > 0)  // 判断当前波及范围左边是否有兔子
			c++;

		m -= c;  // 波及一次消耗萝卜

		a++;  // 给喜爱的兔子萝卜数量+1,波及范围扩大  // 若是剩余萝卜数量少于波及范围的话,肯定剩余萝卜会分到给最喜爱的萝卜
	}

	cout << a;

	return 0;
}

  添加优化代码

	if (n == 1)  // 优化,一个兔子时,所有萝卜给它就完了
	{
		cout << m;
		return 0;
	}

这题就是测试点6没有通过
Test6:
Input
1 1000000000 1
Output
1000000000

经验2 :掌握不了数学逻辑规律再去暴力求解。

  这是我的老毛病了,直接拿下面这道例题来辅助理解这句话的含义。
  数数题
  题目连接:https://vjudge.net/contest/616248#problem/B
  这道题,我看到之后,想都没想,直接使用暴力,遍历每一个数,从每个数的首位开始遍历,若是该数出现两个非0的数,则判断下一个数;否则该数为Round数。

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main()
{

	int t;
	cin >> t;
	while (t--)
	{
		int n;
		cin >> n;
		int cnt = 0;
		for (int i = 1; i <= n; i++)
		{
			string str = to_string(i);
			int count = 0;
			for (int i = 0; i < str.size(); i++)
			{
				if (count == 2)
					break;
				if (str[i] != '0')
					count++;
			}
			if (count < 2)
				cnt++;
		}
		cout << cnt << endl;
	}
	return 0;
}

  代码思路很简单,编写也非常顺利,但是在提交后,我发现了时间超时
  回过头看时,我这里的时间复杂度为O(n^2),确实很高。
  于是乎,我开始寻找有没有更简单的方法。我试着随便找个数,在草稿纸上写出1到该数的所有Round数。我发现了规律:Round数无外乎就是首位不为0,其余位均为0的数。那么,假设给定的是个4位数首位为k,那么1~9+10、20…90、100、00…k00为所有的Round数。根本无需遍历,只要直接计算就可以得到结果。
代码比前面的暴力算法还要好写。

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main()
{

	int t;
	cin >> t;
	while (t--)
	{
		int n;
		cin >> n;
		int cnt = 0;

		string str = to_string(n);
		int num = str.size();
		cnt += (num - 1) * 9;
		int firstW = str[0] - '0';
		cnt += firstW;

		cout << cnt << endl;
	}
	return 0;
}
经验3:与自己学过的知识点相比较。

话不多说,直接上“原点子串”题目连接:https://vjudge.net/contest/616248#problem/D
  我们来分析一下这道题。
1、回到原点,怎么定义回到原点呢?坐标为(0,0)时。那么想当然的,就要定义上下左右移动是什么的。这里定义为URDL分别为(1,0)、(0,1)、(0,-1)、(-1,0)。
2、题目要求求出所有子串。这里科普一下:子串的定义是:连续的一小段。与之相似的名词是子序列。子序列的定义是:不连续的一小段。例如:有字符串abcdefg。那么有子串abc、子串bcd;有子序列abc、abdg。由此可知,子串是子序列,子序列不是子串。
3、我们只需要将子序列每个字符对应的“坐标变化”进行“相加”,判断结果是否为(0,0),即判断是否回到了原点。
4、如果你学过了前缀和,当你理顺了上述3点,你应该就知道用这个方法啦。在输入时就将前面的所有“坐标变化”之和存储起来。后面求子串时,就只需要用某个前缀和来减去另外一个前缀和,非常的方便。

#include<iostream>
#include<vector>
#include<string>
using namespace std;

int main()
{
	int n;
	cin >> n;
	vector<char> ax(n+1);
	vector<int> sx(n+1);
	vector<int> sy(n+1);
	for (int i = 1; i <= n; i++)
	{
		cin >> ax[i];
		int tempX = 0;
		int tempY = 0;
		if (ax[i] == 'U')
		{
			tempX = 1;
			tempY = 0;
		}
		else if (ax[i] == 'R')
		{
			tempX = 0;
			tempY = 1;
		}
		else if (ax[i] == 'L')
		{
			tempX = 0;
			tempY = -1;
		}
		else
		{
			tempX = -1;
			tempY = 0;
		}
		sx[i] = sx[i - 1] + tempX;
		sy[i] = sy[i - 1] + tempY;
	}

	int cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		for (int j = i - 1; j >= 0; j--)
		{
			if (sx[i] - sx[j] == 0 && sy[i] - sy[j] == 0)
				cnt++;
		}
	}

	cout << cnt;
	return 0;
}

  顺带再记录一下写出来的另外两道题目。

A-错误的减法https://vjudge.net/contest/616248#problem/A

#include<iostream>
#include<vector>
using namespace std;
int main()
{

	int n;
	int k;
	cin >> n>>k;
	while (k--)
	{
		if (n % 10 != 0)
			n--;
		else
		{
			n /= 10;
		}
	}
	cout << n;
	return 0;
}

  这道题很简单,没什么好说的。

C-字符串划分https://vjudge.net/contest/616248#problem/C
  这道题一开始还不知道怎么写呢。后面还是沉下心来,拿草稿纸演算并思考了一下:
1、我们可以组成什么字符串。我们拥有aa、bb、aaa、bbb。那么,我们便可以组成2~无穷个连续的a字符串和b字符串。
2、我们组成不了什么字符串。单个a和单个b。
3、有了上面两个发现,我们便得出了判断能否组成字符串的方法:统计某个字符连续的次数,若次数为1,则输出NO;否则遍历它后面的字符,接着重复判断重复出现的次数,知道输出NO或者是字符串的每个字符遍历完了。

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		string str;
		cin >> str;
		int x = 1;
		bool flag = 0;
		for (int i = 1; i < str.size(); i++)
		{
			if (str[i] == str[i - 1])
			{
				x++;
				continue;
			}
			else
			{
				if (x <= 1)
				{
					cout << "NO" << endl;
					flag = 1;
					break;
				}
				else
				{
					x = 1;
				}
			}
		}
		if (x > 1)
			cout << "YES" << endl;
		else if(!flag)  // 单个字符时,在这里进行输出!!!
			cout << "NO" << endl;
	}
	return 0;
}

  这里又有话说了,一开始我没有考虑单个字符的情况,要不是测试样例中有单个字符,我就要提交上去送分了!!!所以,我又得出了如下代码。

经验4:自己写多几个测试样例,重复考虑特殊情况,以检验代码是否完整。

  写在最后,很高兴您能看到这里。
  如果您是大三,和我一样还没有练过几道算法题(我仅练习了半个月),那么我强烈建议如果不考研的同学的可以每天写一写算法题,已写带学。
  如果您是大一大二甚至是高中的学弟,我更加强烈建议你们现在开始每天学算法、练学法,不要想着自己的基础知识还没学完,自己的《数据结构》没学或者是《算法设计》没学。过来人告诉你,就算等你到时候学了,你没有练习,你也会和我一样,啥都不会。什么动态规划、贪心算法,你就会知道你上课听过,但是你没有动手写过,你不能深刻理解其中的微妙。
  来吧!和我一起练起来,你以后考研或者是就业,肯定都有些许帮助。

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值