找点事做,做了一下蓝桥杯模拟赛,写个简单的题解以及对应的AC代码
整体难度不大,最后一题的难度还是挺大的
比赛题目链接 : http://oj.hzjingma.com/contest/view?id=20
目录
A,战疫情
直接暴力枚举所有情况
也就是,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,使得每个单词的价值最大。
那么根据贪心的额思想,当我们计算了,如果其值大的,我们分配大的ID,那么最后的价值肯定是最大的。
所以题目最后就变为,计算每一个单词的,然后从小到大排序,那么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,计算器
按照题意模拟,即最后我们要得到 ,从而得到解 :
所以我们主要就是,根据输入的字符串,分别求出等号两边的,常数项和未知量的系数
注意的是,我们对输入的字符串从前往后判断,还是从后往前判断?
如果从前往后判断,当我遇到 数字的时候,我们无法直接判断,这个数字是,常数项还是系数,所以我们从后往前判断
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的规模变为原来的一半,就不会超时。
那么中间点怎么找,由于对称路径,所以我们的中间点找:。
第一次,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;
}