1.既约分数
P1509 - [蓝桥杯2020初赛] 既约分数 - New Online Judge
错误代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int main()
{
LL res = 0;
for (int i = 1; i <=2020; i++) {
for (int j = i+1; j <= 2020; j++) {
if (gcd(i, j) == 1) res++;
}
}
cout << res*2<< endl;
return 0;
}
思路:写一个最大公约数函数,然后枚举使得分子小于分母的情况,如果满足最大公约数为1,就计数加1,然后再乘2(分母比分子大的情况)
但是忘掉了一种情况,分子等于分母,只有(1,1)
这样,答案少了1,但是对于结果填空题来说,就是错的
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int main()
{
LL res = 0;
for (int i = 1; i <=2020; i++) {
for (int j = i+1; j <= 2020; j++) {
if (gcd(i, j) == 1) res++;
}
}
cout << res*2+1<< endl;
return 0;
}
2.蛇形填数
P1510 - [蓝桥杯2020初赛] 蛇形填数 - New Online Judge
找规律,进行模拟
从第一个数开始,发现每次都是四个过程
1.j++
2.i++,j--一直到j等于1
3.i++
4.i--,j++一直到i等于1
错误代码:
错误原因:数组开小了,因为a[30][30]就够用了,实际上有可能算到a[20][20]的过程中某一可能要超过30
注意:要在每个过程中判断i和j是否同时等于20,否则如果在while循环后面判断的话,i等于1或者j等于1了
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
LL a[30][30];
int main()
{
LL cnt = 1;
a[1][1] = 1;
int i = 1, j = 1;
while (1) {
j++;
a[i][j] = ++cnt;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
while (j != 1) {
i++;
j--;
a[i][j] = ++cnt;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
}
i++;
a[i][j] = ++cnt;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
while (i != 1) {
i--;
j++;
a[i][j] = ++cnt;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
}
}
return 0;
}
修改代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
LL a[100][100];
int main()
{
LL cnt = 1;
a[1][1] = 1;
int i = 1, j = 1;
while (1) {
j++;
a[i][j] = ++cnt;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
while (j != 1) {
i++;
j--;
a[i][j] = ++cnt;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
}
i++;
a[i][j] = ++cnt;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
while (i != 1) {
i--;
j++;
a[i][j] = ++cnt;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
}
}
return 0;
}
发现根本用不着数组,只要枚举i,j就行了
再次修改代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
int main()
{
LL cnt = 1;
int i = 1, j = 1;
while (1) {
j++;
cnt++;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
while (j != 1) {
i++;
j--;
cnt++;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
}
i++;
cnt++;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
while (i != 1) {
i--;
j++;
cnt++;
if (i == 20 && j == 20) {
cout << cnt << endl;
return 0;
}
}
}
return 0;
}
3.跑步锻炼
P1513 - [蓝桥杯2020初赛] 跑步锻炼 - New Online Judge
模拟,枚举年月日
2000年到2020年
对于每年,从1月到12月,
对于每个月,情况不同,从1号到28号或29号或或31号,弄个数组,记录每月的天数30号
日子一天一天增加,星期就随着日子一天一天增加
星期的变化就从1到7,如果到7,就变为0(不要用约瑟夫循环%7,这样反而容易弄错)
注意,闰年的2月是29天,平年的闰月是28天
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
int month[13] = { 0,31,29,31,30,31,30,31,31,30,31,30,31 };
int month1[13] = {0, 31,28,31,30,31,30,31,31,30,31,30,31 };
int main()
{
int weekday=6;
LL res = 0;
for (int i = 2000; i <= 2020; i++) {
if ((i % 4 == 0&&i%100!=0)||i%400==0) {
for (int j = 1; j <=12; j++) {
for (int k = 1; k <= month[j]; k++) {
if (weekday == 1 || k == 1) res += 2;
else res += 1;
if (i == 2020 && j == 10 && k == 1&&weekday==4) {
cout << res << endl;
return 0;
}
if (weekday == 7) weekday = 0;
weekday++;
}
}
}
else {
for (int j = 1; j <=12; j++) {
for (int k = 1; k <= month1[j]; k++) {
if (weekday == 1 || k == 1) res += 2;
else res += 1;
if (weekday == 7) weekday = 0;
weekday++;
}
}
}
}
return 0;
}
4.成绩统计
P1522 - [蓝桥杯2020初赛] 成绩统计 - New Online Judge
四舍五入:
保留一位小数:比如说对71.4四舍五入,只要加上0.5,再取整就可以了
保留两位小数:比如说对71.45四舍五入,只要先使71.45*10=714.5,再加上0.5,再取整,最后/10
保留三位小数:比如说对71.456四舍五入,只要先使71.456*100=7145.6,再加上0.5,再取整,最后/100
总之,先乘10的幂使之只有一位小数,然后加上0.5,取整,最后再除以10的幂
注意:在算的过程中要注意数据类型的转换,需要在中途强制类型转换
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 100010;
int a[N];
int n;
int cnt, cnt1;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
if (a[i] >= 60 ) cnt++;
if (a[i] >= 85) cnt1++;
}
int x = (double)cnt / n*100+0.5;
int y = (double)cnt1 / n*100+0.5;
printf("%d%%\n%d%%\n", x, y);
return 0;
}
5.子串分值和
P1523 - [蓝桥杯2020初赛] 子串分值和 - New Online Judge
代码(过50%样例)
用i,j来枚举连续子串(i代表头,j代表尾),对于某一子串,k从i到j,来判断子串有多少个不同的字符,用桶计数的思想,如果该字符的个数是0,那么不同字符个数计数加1
记得每次枚举完一个子串,要初始化桶
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<string.h>
using namespace std;
typedef long long LL;
const int N = 100010;
char s[N];
int cnt[N];
int main()
{
cin >> s;
LL res = 0;
int n = strlen(s);
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
for (int k = i; k <= j; k++) {
if (cnt[s[k]] == 0) res++;
cnt[s[k]]++;
}
memset(cnt, 0, sizeof cnt);
}
}
cout << res << endl;
return 0;
}
发现数组下标为字符也能运行并且通过样例,应该是字符本身存储的就是整数,所以没关系
不过,可以把cnt[s[k]]改为cnt[s[k]-'a']
优化:对于每个字符,来算它的贡献度,然后把所有贡献度加起来
每个字符只有在第一次出现时才有贡献度,统计每个字符在第一次出现时被多少子串包含
用last[s[i]]记录字符s[i]上一次出现的位置,那么如何算该字符第一次出现时被多少子串所包含呢?将该字符往左数,一直数到相同字符的前一个的个数乘将该字符往右数,一直数到最后一个字符的个数
这样就分成了两段,左边一段是包含该字符的子段,右边一段也是包含该字符的子段,两子段的个数相乘就是包含该字符的总子段个数,也就是该字符的贡献度(可以找一个样例来试一下,发现是这样的)
注意,在运算的过程中可能会爆int,因此需要强制类型转换成long long
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 100010;
int last[N];
int main()
{
string s;
cin >> s;
int n = s.size();
LL res = 0;
s = ' ' + s;
for (int i = 1; i <= n; i++) {
res += (LL)(i - last[s[i]]) * (n - i + 1);
last[s[i]] = i;
}
cout << res << endl;
return 0;
}
6.平面切分
P1524 - [蓝桥杯2020初赛] 平面切分 - New Online Judge
思路:
一条直线如果与之前的直线没有交点,那么增加一个部分
如果与之前的直线有交点,那么在增加一个部分的基础上,产生了几个交点(可能与这条直线的交点和那条直线的交点一样,这样算是一个交点,因此需要去重,用set容器),就再增加几个部分
如果与之前的某一条直线重合了的话,那么由于之前已经算过了,所以就不再增加新的部分了
具体操作:用结构体储存斜率和截距,然后枚举每一条直线,对于每一条直线,如果和之前的某一条直线重复的话,就break,
并标记为false,如果平行的话,就continue,如果有交点的话。就算出交点坐标,并将坐标放入set容器里,最后加上set容器里坐标个数再加1来算出枚举的该条直线增加了几部分
将所有的直线增加的部分加起来,再加上一开始的一个部分,就是总共的部分
set容器:set就相当于一个集合,里面的元素是有序的且独一无二的
头文件:#include
set变量的定义:set<int>s;
操作:
注意定义set变量的位置,放在i循环的下面,即对于每一条直线,都重新弄一个set容器,来存储产生的交点个数
#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
typedef long long LL;
typedef pair<double, double>PII;
const int N = 1010;
int n;
struct Line {
double k, b;
}l[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> l[i].k >> l[i].b;
}
LL res = 1;
for (int i = 1; i <= n; i++) {
set<PII>q;
bool flag = true;
for (int j = 1; j <= i - 1; j++) {
if (l[i].k == l[j].k ) {
if (l[i].b == l[j].b) {
flag = false;
break;
}
else continue;
}
PII t;
t.first = (l[i].b - l[j].b) / (l[j].k - l[i].k);
t.second = l[i].k * t.first + l[i].b;
q.insert(t);
}
if (flag) res += q.size() + 1;
}
cout << res << endl;
return 0;
}
7.回文日期
P1518 - [蓝桥杯2020初赛] 回文日期 - New Online Judge
对于每一年,它的回文日期就确定了,只要判断日期合不合法就行了
先将年份的四位数分别取出,利用取十进制的方法算出回文的月和日,判断是否合法
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
int m1[13] = { 0,31,29,31,30,31,30,31,31,30,31,30,31 };
int m2[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int ans,ans1,ans2;
int main()
{
int t;
cin >> t;
while (t--) {
int nowday;
cin >> nowday;
int now = nowday / 10000;
bool flag1 = false, flag2 = false;
for (int i = now ; i <= 9999; i++) {
int n = i;
int a = n % 10;
int b = n / 10 % 10;
int c = n / 100 % 10;
int d = n / 1000;
int month = a * 10 + b;
int day = c * 10 + d;
int ans = n * 10000 + month*100 +day;
if (ans <= nowday) continue;
if (n % 4 == 0 && n % 100 != 0 || n % 400 == 0) {
if (month >= 1 && month <= 12 && day >= 1 && day <= m1[month]) {
if (!flag1) {
ans1 = ans;
flag1 = true;
}
if (a == c && b == d) {
ans2 = ans;
flag2 = true;
}
}
}
else {
if (month >= 1 && month <= 12 && day >= 1 && day <= m2[month]) {
if (!flag1) {
ans1 = ans;
flag1 = true;
}
if (a == c && b == d) {
ans2 = ans;
flag2 = true;
}
}
}
if (flag1 && flag2) break;
}
cout << ans1 << endl;
cout << ans2 << endl;
}
return 0;
}
8.七段码
P1511 - [蓝桥杯2020初赛] 七段码 - New Online Judge
在csdn上看了大佬(业余算法学徒)的文章,用了两种方法
法一:将所有情况打印出来
由于一共只有七段,这刚好符合二进制的表示,二进制的表示一共是7位(符合七段),每一位用0和1表示(符合每一段的亮与灭)
只要用七位分别表示七段的图案就行了
二进制表示范围为0到127
x>>i&1表示二进制表示中第i位是多少(最低位是第0位)
具体见位运算
(6条消息) 算法:位运算_m0_74087709的博客-CSDN博客https://blog.csdn.net/m0_74087709/article/details/128595662
数一下:一共有127种,有47种不联通,那么有80种联通
#include <iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main()
{
int cnt = 1;
for (int x = 1; x <= 127; x++)
{
cout << "====" << cnt++ << "====" << endl;
for (int i = 0; i < 7; i++)
{
if (i == 0)
{
if (x >> i & 1) cout << " --";
cout << endl;
}
if (i == 1)
{
if (x >> i & 1) cout << '|';
else cout << " ";
}
if (i == 2)
{
if (x >> i & 1) cout << " |";
cout << endl;
}
if (i == 3)
{
if (x >> i & 1) cout << " --";
cout << endl;
}
if (i == 4)
{
if (x >> i & 1) cout << '|';
else cout << " ";
}
if (i == 5)
{
if (x >> i & 1) cout << " |";
cout << endl;
}
if (i == 6)
{
if (x >> i & 1) cout << " --";
cout << endl;
}
}
cout << endl;
}
return 0;
}
法二:dfs&&并查集
用1到7表示a到g,用邻接矩阵在相邻的两段连上无向边
用dfs枚举所有的亮灭情况(类似于(7条消息) atcoder ABC 128_m0_74087709的博客-CSDN博客),用并查集判断是否只有一个联通块
连边的时候,对于每一段去连它的边,这样能做到不重不漏
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 10;
int e[N][N];
int st[N];
int p[N];
int res;
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void dfs(int x) {
if (x <= 7) {
st[x] = 1;
dfs(x + 1);
st[x] = 0;
dfs(x + 1);
}
else {
for (int i = 1; i <= 7; i++) p[i] = i;
for (int i = 1; i <= 7; i++) {
for (int j = 1; j <= 7; j++) {
if (e[i][j] && st[i] && st[j]) {
p[find(i)] = find(j);
}
}
}
int cnt = 0;
for (int i = 1; i <=7; i++) {
if (st[i]&&p[i] == i) cnt++;
}
if (cnt == 1) res += 1;
return;
}
}
int main()
{
e[1][2] = e[1][6] = 1;
e[2][1] = e[2][3] = e[2][7] = 1;
e[3][2] = e[3][4] = e[3][7] = 1;
e[4][3] = e[4][5] = 1;
e[5][4] = e[5][6] = e[5][7] = 1;
e[6][5] = e[6][1] = e[6][7] = 1;
e[7][2] = e[7][3] = e[7][5] = e[7][6] = 1;
dfs(1);
cout << res << endl;
return 0;
}