【算法比赛】竞码编程-蓝桥杯模拟赛

找点事做,做了一下蓝桥杯模拟赛,写个简单的题解以及对应的AC代码

整体难度不大,最后一题的难度还是挺大的

 

比赛题目链接 : http://oj.hzjingma.com/contest/view?id=20


目录

A,战疫情

B,行动

C,莱布尼茨公式

D,价值之和

E,数方

F,你好,2020

G,最优值

H,计算器

I,对称迷宫

J,因数个数


 

A,战疫情

直接暴力枚举所有情况

20000\leq x + y \leq 21000

也就是,x 从 [0, 21000]枚举,然后  y 从 [0, 21000],

然后判断,要求 x + y 在上述区间,同时 2 * x + 100 * y == 50000 时,答案 + 1。最后得到答案。

 

// 答案 : 21

#include<iostream>
using namespace std;

int main(){
	
	int ans = 0;
	
	for(int x = 0; x <= 21000;++x){
		for(int y = 0;y <= 21000;++y)
		{
			if(x + y < 20000) continue;
			if(x + y > 21000) break;
			
			if(2 * x + 100 * y == 50000) ++ans;
		}
	}
	cout << ans << endl;
	
	return 0;
} 

 

B,行动

根据题目,模拟整个过程,而走的存在,四个方向的可能,根据规律,(1,2,3,4),(5,6,7,8)...

我们可以枚举 i 从 1 到 2020,然后根据 i % 4的余数,得到四个方向变化

1)i % 4 == 1,x += i

2)i % 4 == 2,y -= i

3)i % 4 == 3,x -= i

4)i % 4 == 0,y += i

最后得到答案,甚至,我们可以根据手算找规律得到答案,2020是4的倍数,所以最后的落地点是在第二象限,根据一开始的规律,在走第 4 轮的时候,左边为(-2, 2),因此最后的答案应该是 (-1010, 1010)

 

// 答案:-1010 1010

#include<iostream>
using namespace std;

int main(){
	
	int x = 0, y = 0;
	
	for(int i = 1;i <= 2020;++i)
	{
		if(i % 4 == 1){
			x += i;
		}
		if(i % 4 == 2){
			y -= i;
		}
		if(i % 4 == 3){
			x -= i;
		}
		if(i % 4 == 0){
			y += i; 
		}
	}
	cout << x << " " << y << endl;
	
	return 0;
} 

 

C,莱布尼茨公式

根据题目模拟即可,得到整除相除想为数,应该是 8 / 1.0 / 9。

 

// 答案 : 3.141098

#include<iostream>
#include<cstdio>
using namespace std;

int main(){
	
	double res = 0;
	bool flag = true;
	for(int i = 1;i <= 4039; i+= 2)
	{
		if(flag)
			res += 4.0 / i;
		else
			res -= 4.0 / i;
		
		flag = !flag;
		
	}
	printf("%0.6f\n", res);
	return 0;
} 

 

D,价值之和

暴力枚举从 1 到 2020 的每个数,如果这个数包含 数字5 就跳过。

当计算某个数的价值时,由于时求质因子,所以我们可以先利用质数筛选的方法,把 2020 内的所有质数找出来,然后对当前数 i ,枚举每一个质数,判断是不是它的因子,是的话,相当于价值 + 1。

 

//答案:3257

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N = 2020;
int p[3000];
vector<int> allP;

void init()  // 把 2020 内的所有质数找出来
{
	memset(p, 0, sizeof(p));
	for(int i = 2;i <= N;++i)
	{
		if(p[i] == 0)
		{
			for(int j = i * 2;j <= N;j += i)
				p[j] = 1;
		}
	}
	for(int i = 2;i <= N;++i)
	{
		
		if(p[i]==0)
		{
			allP.push_back(i);
			// cout << i << endl;
		}
			
	}
	//cout << allP.size() << endl;
}

int calVal(int num)  // 计算对应这个值,有多少个质因子
{
	int res = 0; 

	for(int i = 0;i < allP.size();++i)
	{
		
		if(num % allP[i] == 0)
		{
			++res;
		}
		
	}
	return res;
}

bool isFive(int num)  // 判断这个数有没有 5.
{
	while(num)
	{
		if(num % 10 == 5) return true;
		num /= 10;
	}
	return false;
}

int main()
{
	init();
	
	int res = 0;
	// cout << calVal(N) << endl;

	for(int i = 2;i <= N;++i)
	{
		if(isFive(i) == true) continue;
		res += calVal(i);
	}

	cout << res << endl;
	
	return 0;
} 

 

E,数方

直接暴力枚举所有可能,然后检验 6 个条件是否满足

循环次数 (9 ^ 9)*(条件判断) = (4*10^8)*(条件判断) ≈ 4*10^10,还是可以计算出来的

是可以计算的

 

//答案: 
// 7 2 9
// 4 5 7
// 1 6 9

#include<iostream>
#include<vector>
#include<cstring>
#include<cmath>
#include<string>
#include<cstdio>
using namespace std;

bool ABC(int num)
{
	for(int j = 5;j < 10;++j)
	{
		if(j * j * j == num)
			return true;
	}
	return false;
}

bool DEF(int num)
{
	for(int i = 2;i <= (int)sqrt(num);++i)
	{
		if(num % i == 0)
			return false;
	}
	return true;
}

bool GHI(int num)
{
	int i = sqrt(num);
	if(i * i == num)
		return true;
		
	return false;
}

bool ADG(int num)
{
	int sum = 0;
	for(int i = 1;i < 100; ++i)
	{
		sum += i;
		if(sum == num) return true;
		
		if(sum > num) return false; 
	}
	return false;
}

bool BEH(int num)
{
	for(int i = 3;i <= 8; ++i)
	{
		if(i * i *i *i == num) return true;
	}
	return false;
}

bool CFI(int num)
{
	int a = num % 10;
	int b = num / 100;
	if(a == b) return true;
	
	return false;
}


bool isCan(int a, int b, int c, int d, int e, int f, int g, int h, int i)
{
	int v1 = a * 100 + b * 10 + c;
	int v2 = d * 100 + e * 10 + f;
	int v3 = g * 100 + h * 10 + i;
	int v4 = a * 100 + d * 10 + g;
	int v5 = b * 100 + e * 10 + h;
	int v6 = c * 100 + f * 10 + i;
	
	return (ABC(v1) && DEF(v2) && GHI(v3) && ADG(v4) && BEH(v5) && CFI(v6));
	
}


int main()
{
	int res = 0;
	for(int A = 1;A <= 9;++A)
		for(int B = 1;B <= 9;++B)
			for(int C = 1;C <= 9;++C)
				for(int D = 1;D <= 9;++D)
					for(int E = 1;E <= 9;++E)
						for(int F = 1;F <= 9;++F)
							for(int G = 1;G <= 9;++G)
								for(int H = 1;H <= 9;++H)
									for(int I = 1;I <= 9;++I)
									{
										if(isCan(A, B, C, D, E, F, G, H, I) == true)
										{
											printf("%d %d %d %d %d %d %d %d %d\n", A, B, C,D,E,F,G,H,I);
											return 0; 
										}
									}
	cout << "No" << endl;
	return 0;
} 

 

F,你好,2020

我们根据,一个数,从中间分开,那么只要我们枚举前一半的数,那么后一半的数是相同的,根据数据范围,n 最大 是 10^6,那么最大数应该是 999999,所以我们通过枚举 1 到 999,这时得到的数,我们要计算当前这个数有几位,比如 25,是两位,那么得到的数为 25 * 100 + 25 = 2525

因此,我们通过枚举1 到 999,然后计算,此时的通过这个数,变为 num * 10... + num,才是要求的和的数,期间,num * 10..0,有几个 0 ,通过计算 num 是几位数得来的。

而且,当我们计算出来的数 num * 10... + num > n,说明后面再大的数,也不在 n 的范围了,那么再枚举也没用了,就提前结束枚举。

 

#include<iostream>
#include<vector>
#include<cstring>
#include<cmath>
#include<string>
#include<cstdio>
using namespace std;

int calVal(int num)
{
	int res = 0;
	int temp = num;
	int p = 1;
	while(temp)  // 需要计算位数
	{
		p *= 10;
		temp /= 10; 
	}
	res = num * p + num;
	return res;
}

int main()
{
	int n;
	cin >> n;
	int res = 0;
	for(int i = 1;i <= 999; ++i)
	{
		int v = calVal(i);  // 通过枚举 i,得到 i * 10..0 + i 这个数
		if(v > n) break;
		res += v;
	}
	cout << res;
	return 0;
} 

 

G,最优值

根据题意,要使得总价值最大,也就是分配 ID,使得每个单词的价值最大。

那么根据贪心的额思想,当我们计算了|ch| * L,如果其值大的,我们分配大的ID,那么最后的价值肯定是最大的。

所以题目最后就变为,计算每一个单词的|ch| * L,然后从小到大排序,那么ID就从小到大分配为 1...N。

注意的地方是,根据数据范围,单个单词的最大价值为 26 * 20 * 10^5 ≈ 6 *10 ^ 7,那么从价值之和,最理想的额最大值为 6 *10 ^ 7 * 10 ^ 5 = 6 *10 ^(12),这个范围超过了 int ,所以最后计算得到的总价值应该用 long long 保存。

 

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cmath>
#include<string>
#include<cstdio>
using namespace std;
vector<string> words; 
vector<int> vals;

int main()
{
	// freopen("input.txt", "r", stdin);
	int n;
	cin >> n;
	for(int i = 0;i < n;++i) // 输入每个单词
	{
		string s;
		cin >> s;
		words.push_back(s);
	}
	
	for(int i = 0;i < n; ++i)  // 每个单词先计算 |ch| * L
	{
		int v = words[i].size() * (words[i][0] - 'a' + 1);
		vals.push_back(v);
	}
	sort(vals.begin(), vals.end());  // 排序
	
	long long ans = 0;
	for(int i = 0;i < n;++i)  // 从小到大分配ID,同时计算每个单词的价值,最后累加。
	{
		ans += vals[i] * (i + 1);
	}
	cout <<  ans;
	return 0;
} 

 

H,计算器

按照题意模拟,即最后我们要得到 A + a * x = B + b * x,从而得到解 : x = \frac{B - A}{a - b}

所以我们主要就是,根据输入的字符串,分别求出等号两边的,常数项和未知量的系数

注意的是,我们对输入的字符串从前往后判断,还是从后往前判断?

如果从前往后判断,当我遇到 数字的时候,我们无法直接判断,这个数字是,常数项还是系数,所以我们从后往前判断

1)如果找到的是,字母,那么把字母保存,同时继续往前把系数得到

2)如果找到的是,数字,那么继续往前把数字(常数项)得到

那么此时就是如何得到数字,从数字开始,继续往前判断,直到出现  +, -, =之类的额,数字才结束。比如 -256,先得到 6,然后,得到 5(5*10 + 6),再得到 2(2*100+5*10+6)。

所以我们都是用A,a保存常数项和系数,如果判断到 = ,说明右边判断完了,那么 B = A,b = a,A = 0, a = 0。继续判断左边

 

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cmath>
#include<string>
#include<cstdio>
using namespace std;

int main()
{
	// freopen("input.txt", "r", stdin);
	
	string s;
	cin >> s;
	
	char x;
	int A = 0, B = 0;  // 常数 : B - A 
	int a = 0, b = 0;  // 变量系数: a - b 
	int val = 0;
	int flag = 1;  // 正负号 
	int p = 1;   // 10的几次幂 
	int n = s.size();
	
	for(int i = n - 1;i >= 0;--i)
	{
		val = 0;  // 把数字转化成int 
		flag = 1;  // 正负号判断 
		p = 1;   // 10的幂 
		
		if(s[i] == '=')
		{
			B = A;
			b = a;
			A = 0;
			a = 0;
			continue;
		}
		
		if(s[i]>='a' && s[i]<='z')  //判断是字母,所以前面的数字是系数 
		{
			x = s[i];
			--i;
			// 如果字母前,直接是非数字,那么系数是 1
			if(i < 0 || s[i]<'0' || s[i]>'9')   
			{
				val = 1;
			}
			else
			{
				while(i >= 0 && s[i]>='0' && s[i]<='9')
				{
					val += (s[i] - '0') * p;
					p *= 10;
					--i;
				}
			}
			
			if(s[i] == '+' || s[i] =='-')
			{
				if(s[i]=='-') flag = -1;
			}
			// 如果数字前面不是 +,-,那么可能是=,那么相当于 = 的 i 要返回,外面判断
			// 同时,正负号是 +1。 
			if(s[i] == '=') ++i;

			a += val * flag; 
		}
		else
		{
			while(i >= 0 && s[i]>='0' && s[i]<='9')
			{
				val += (s[i] - '0') * p;
				p *= 10;
				--i;
			}
			if(s[i] == '+' || s[i] =='-')
			{
				if(s[i]=='-') flag = -1;
			}
			if(s[i] == '=') ++i;

			A += val * flag; 
		}
		
	}
	
	//cout << A << " " << B << endl;
	//cout << a << " " << b << endl; 
	double res = (B - A)*1.0 / (a - b);
	printf("%c=%0.3f\n", x, res);
	return 0;
} 

 

I,对称迷宫

一开始的想法,直接DFS搜索所有路径,然后判断路径是不是对称,同时用map进行存储(可以方便去重),这种方法,在数据范围,N <= 11 时,可以解决,但是在 N <= 18,会超时 TLE。

所以要想办法,怎么降低时间复杂度,我们得知,路径是不是对称,也就是,从起点往终点走,和终点往起点走要相同。

但是如果直接从起点往终点走,和终点往起点走,会有重复部分,如果我们可以去掉重复部分就好了。

想到,我们可以找到中间点,那么起点到中间点,终点到中间点,分两次dfs,每次dfs的N的规模变为原来的一半,就不会超时。

那么中间点怎么找,由于对称路径,所以我们的中间点找:x + y == n

第一次,dfs,我们从起点到中间点,把走到中间点的路径path保存,同时保存中间点(因为中间点有多个,我们从前往中间点和从后往中间点走,中间点要一样,才会是完整路径)。

第二次dfs,我们走到中间点,同时得到路径path,判断这个路径path和对应的中间点,前面存不存在,如果存在,那么答案 + 1,同时只要再出现这个path(无论是不是相同中间点,都会重复了)。

 

#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cstring>
#include<cmath>
#include<string>
#include<cstdio>
using namespace std;
const int MAXN = 20;

vector<string> mat; 
map<string, int> mp;   // 路径存不存在 
map<string, int> pos[MAXN];  // 中间点和路径 

int ans = 0;
int n;

int dx[2] = {0, 1}, dy[2] = {1, 0};

void dfsFront(int x, int y, string path)
{
	if(x + y == n - 1)
	{
		mp[path] = 1;
		pos[x][path] = 1;
		return;
	} 
	for(int i = 0;i < 2;++i)
	{
		int xx = x + dx[i], yy = y + dy[i];
		
		if(xx < 0 || xx >= n || yy < 0 || yy >= n) continue;
		
		dfsFront(xx, yy, path + mat[xx][yy]);
	}
	
}


void dfsBack(int x, int y, string path)
{
	if(x + y == n - 1)
	{
		if(pos[x][path] && mp[path])  // 只要中间点和路径都在,同时这个path没有重复 
		{
			ans++;
			mp[path] = 0;   //答案 + 1.,同时这个路径再出现,就不计算了 
		}
		
		return;
	} 
	for(int i = 0;i < 2;++i)
	{
		int xx = x - dx[i], yy = y - dy[i];
		
		if(xx < 0 || xx >= n || yy < 0 || yy >= n) continue;
		
		dfsBack(xx, yy, path + mat[xx][yy]);
	}
	
}

int main()
{
	// freopen("input.txt", "r", stdin);
	cin >> n;
	for(int i = 0;i < n;++i)
	{
		string s;
		cin >> s;
		mat.push_back(s);
	}

	dfsFront(0, 0, "" + mat[0][0]);
	dfsBack(n - 1, n - 1, "" + mat[n - 1][n - 1]);
	
	cout << ans;
	return 0;
} 

 

J,因数个数

根据数据范围,能解决5 * 10^ 6

我们利用素数筛选的原理,两重循环,第一重 i : 2 到 n,第二重,j = 2 * i,到 n(间隔 i ),也就是,当我们找到 i,那么 i 的倍数的因子个数 + 1,因为 i 是所有 i 的倍数的因子。

然后再排序,所以时间复杂度是 O(N * logN),所以对于百分之 60 的数据是可以通过的

 

剩下的,可以具体看题解 : http://oj.hzjingma.com/contest/editorial?id=20

 

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#include<cmath>
#include<string>
#include<cstdio>
using namespace std;
const int MAXN = 5e6 + 15;

int p[MAXN];

void init(int n)
{
	memset(p, 0, sizeof(p));
	for(int i = 2;i <= n;++i)
	{
		for(int j = 2 * i; j <= n; j += i)
		{
			p[j]++;
		}
	}
	
}

int main()
{
	int n, k;
	cin >> n >> k;
	init(n);

	sort(p, p+ n + 1);
	
	cout << p[k + 1] + 2 << endl;
	
	return 0;
} 

 

时间复杂度(渐近时间复杂度的严格定义,NP问题,时间复杂度的分析方法,主定理)   排序算法(平方排序算法的应用,Shell排序,快速排序,归并排序,时间复杂度下界,三种线性时间排  序,外部排序)   数论(整除,集合论,关系,素数,进位制,辗转相除,扩展的辗转相除,同余运算,解线性同余方程,中国剩余定理) 指针(链表,搜索判重,邻接表,开散列,二叉树的表示,多叉树的表示) 按位运算(and,or,xor,shl,shr,一些应用) 图论(图论模型的建立,平面图,欧拉公式与五色定理,求强连通分量,求割点和桥,欧拉回路,AOV问题,AOE问题,最小生成树的三种算法,最短路的三种算法,标号法,差分约束系统,验证二分图,Konig定理,匈牙利算法,KM算法,稳定婚姻系统,最大流算法,最小割最大流定理,最小费用最大流算法) 计算几何(平面解几及其应用,向量,点积及其应用,叉积及其应用,半平面相交,求点集的凸包,最近点对问题,凸多边形的交,离散化与扫描) 数据结构(广度优先搜索,验证括号匹配,表达式计算,递归的编译,Hash表,分段Hash,并查集,Tarjan算法,二叉堆,左偏树,二斜堆,二项堆,二叉查找树,红黑树,AVL平衡树,Treap,Splay,静态二叉查找树,2-d树,线段树,二维线段树,矩形树,Trie树,块状链表) 组合数学(排列与组合,鸽笼原理,容斥原理,递推,Fibonacci数列,Catalan数列,Stirling数,差分序列,生成函数,置换,Polya原理) 概率论(简单概率,条件概率,Bayes定理,期望值) 矩阵(矩阵的概念和运算,二分求解线性递推方程,多米诺骨牌棋盘覆盖方案数,高斯消元) 字符串处理(KMP,后缀树,有限状态自动机,Huffman编码,简单密码学) 动态规划(单调队列,凸完全单调性,树型动规,多叉转二叉,状态压缩类动规,四边形不等式) 博奕论(Nim取子游戏,博弈树,Shannon开关游戏) 搜索(A*,ID,IDA*,随机调整,遗传算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值