算法刷题笔记

报名了2025年的蓝桥杯C++ A组,趁着寒假没什么事,一边学习算法一边刷题。万事开头难!


洛谷题单

【入门1】顺序结构

P1000 超级玛丽游戏

题目描述

超级玛丽是一个非常经典的游戏。请你用字符画的形式输出超级玛丽中的一个场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
                ********
************
####....#.
#..###.....##....
###.......###### ### ###
........... #...# #...#
##*####### #.#.# #.#.#
####*******###### #.#.# #.#.#
...#***.****.*###.... #...# #...#
....**********##..... ### ###
....**** *****....
#### ####
###### ######
##############################################################
#...#......#.##...#......#.##...#......#.##------------------#
###########################################------------------#
#..#....#....##..#....#....##..#....#....#####################
########################################## #----------#
#.....#......##.....#......##.....#......# #----------#
########################################## #----------#
#.#..#....#..##.#..#....#..##.#..#....#..# #----------#
########################################## ############
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<iostream>
using namespace std;

int main()
{
cout<<R"( ********
************
####....#.
#..###.....##....
###.......###### ### ###
........... #...# #...#
##*####### #.#.# #.#.#
####*******###### #.#.# #.#.#
...#***.****.*###.... #...# #...#
....**********##..... ### ###
....**** *****....
#### ####
###### ######
##############################################################
#...#......#.##...#......#.##...#......#.##------------------#
###########################################------------------#
#..#....#....##..#....#....##..#....#....#####################
########################################## #----------#
#.....#......##.....#......##.....#......# #----------#
########################################## #----------#
#.#..#....#..##.#..#....#..##.#..#....#..# #----------#
########################################## ############)";

return 0;
}

该题用到了C++11 的raw string literal 技术,也叫作原始字符串技术。需要注意的是R"()"中的第一行不可以是空格,否则会将空格也识别为字符串。


P5704 字母小写转换为大写

题目描述

输入一个小写字母,输出其对应的大写字母。例如输入 q[回车] 时,会输出 Q。

样例输入 #1

1
q

样例输出 #1

1
Q
1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
using namespace std;

int main()
{
char a;
cin >> a;
cout <<char(a-32);

return 0;
}

该题用到了ASCII码的数值转换,字母在进行相加减操作时,实际上是对应的ASCII码值之间的相加减。小写字母的ASCII码值要比大写字母的ASCII码值大32。


P5705 数字反转

题目描述

输入一个不小于 $100$ 且小于 $1000$,同时包括小数点后一位的一个浮点数,例如 $123.4$ ,要求把这个数字翻转过来,变成 $4.321$ 并输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
#include<algorithm> //需要用到该库
using namespace std;

int main()
{
string a;
cin >> a;
reverse(a.begin(), a.end()); //将字符串逆置
cout << a;


return 0;
}

该题用到了C++ STL标准模板库中的reverse()函数,该函数可以将字符串逆置。


P5706 再分肥宅水

题目描述

现在有 $t$ 毫升肥宅快乐水,要均分给 $n$ 名同学。每名同学需要 $2$ 个杯子。现在想知道每名同学可以获得多少毫升饮料(严格精确到小数点后 $3$ 位),以及一共需要多少个杯子。

输入格式

输入一个实数 $t$ 和一个正整数 $n$,使用空格隔开。

输出格式

输出两行。

第一行输出一个三位小数,表示可以获得多少毫升饮料。第二行输出一个正整数,表示一共需要多少个杯子。

样例输入 #1

1
500.0 3

样例输出 #1

1
2
166.667
6

提示

对于所有数据,$0\leq t\leq 10000$ 且小数点后不超过 $3$ 位,$1\leq n\leq 1000$ 。

1
2
3
4
5
6
7
8
9
10
11
12
#include<bits/stdc++.h> //万能头文件,STL库中的所有函数都可以直接调用
using namespace std;

int main()
{
double t;
int n;
cin >> t >> n;
cout << setprecision(3) << fixed << t / n << endl;
cout << n * 2;
return 0;
}

setprecision(3) 设置 浮点数输出的总有效数字位数,即小数点前后所有数字的总个数。它控制 输出时的精度,确保总共显示 3 位有效数字。

  • 需要注意的是,setprecision 在默认情况下并不强制小数部分显示特定数量的数字,而是控制总共有效数字位数。

    但是当 fixed 被使用时,setprecision(n) 会控制小数点后 显示 n 位数字

    fixedsetprecision要求数值为浮点类型(如double)才能正确控制小数位数。

    如果是double a = 500 / 3,仍然不能保证a是浮点数,因为500/3是整数除法,所以要把分子或分母的其中一个数手动变为浮点数,例如double a = 500.0 / 3或double a = 500 / 3.0。


P5708 三角形面积

题目描述:

一个三角形的三边长分别是 $a$、$b$、$c$,那么它的面积为 $\sqrt{p(p-a)(p-b)(p-c)}$,其中 $p=\frac{1}{2}(a+b+c)$。输入这三个数字,计算三角形的面积,四舍五入精确到 $1$ 位小数。

输入格式

第一行输入三个实数 $a, b ,c$,以空格隔开。

输出格式

输出一个实数,表示三角形面积。精确到小数点后 $1$ 位。

样例输入 #1

1
3 4 5

样例输出 #1

1
6.0

提示

数据保证能构成三角形,$0\leq a,b,c\leq 1000$,每个边长输入时不超过 $2$ 位小数。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>
using namespace std;

int main()
{
double a, b, c, p, ans;
cin >> a >> b >> c;
p = (a + b + c) / 2;
ans = sqrt(p * (p - a) * (p - b) * (p - c)); //开平方根
cout << fixed << setprecision(1) << ans;

return 0;
}

该题用到了sqrt()开平方根函数


B2029 大象喝水

题目描述

一只大象口渴了,要喝 $20$ 升水才能解渴,但现在只有一个深 $h$ 厘米,底面半径为 $r$ 厘米的小圆桶 ($h$ 和 $r$ 都是整数)。问大象至少要喝多少桶水才会解渴。

Update:数据更新,这里我们近似地取圆周率 $\pi = 3.14$。

输入格式

输入有一行:包行两个整数,以一个空格分开,分别表示小圆桶的深 $h$ 和底面半径 $r$,单位都是厘米。

输出格式

输出一行,包含一个整数,表示大象至少要喝水的桶数。

样例输入 #1

1
23 11

样例输出 #1

1
3

数据规模与约定

对于全部的测试点,保证 $1 \leq h \leq 500$,$1 \leq r \leq 100$。

1
2
3
4
5
6
7
8
9
10
11
12
#include<bits/stdc++.h>
using namespace std;

int main()
{
double h, r, v;
cin >> h >> r;
v = 3.14 * r * r * h;
cout << int(20000 / v)+1; //强制转换为整数类型

return 0;
}

该题不难,关键在于输出的结果要强制转换为整数类型。


P1425 小鱼的游泳时间

题目描述

伦敦奥运会要到了,小鱼在拼命练习游泳准备参加游泳比赛,可怜的小鱼并不知道鱼类是不能参加人类的奥运会的。

这一天,小鱼给自己的游泳时间做了精确的计时(本题中的计时都按 $24$ 小时制计算),它发现自己从 $a$ 时 $b$ 分一直游泳到当天的 $c$ 时 $d$ 分,请你帮小鱼计算一下,它这天一共游了多少时间呢?

小鱼游的好辛苦呀,你可不要算错了哦。

输入格式

一行内输入四个整数,以空格隔开,分别表示题目中的 $a, b, c, d$。

输出格式

一行内输出两个整数 $e$ 和 $f$,用空格间隔,依次表示小鱼这天一共游了多少小时多少分钟。其中表示分钟的整数 $f$ 应该小于 $60$。

样例输入 #1

1
12 50 19 10

样例输出 #1

1
6 20

提示

对于全部测试数据,$0\le a,c \le 24$,$0\le b,d \le 60$,且结束时间一定晚于开始时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<bits/stdc++.h>
using namespace std;

int main()
{
double a, b, c, d, e, f;
cin >> a >> b >> c >> d;
if (b > d)
{
e = c - a - 1;
f = 60 + d - b;
}
else
{
e = c - a;
f = d - b;
}
cout << e <<" " << f;
return 0;
}

该题我使用了if判断语句。

如果结束时间的分钟数<开始时间的分钟数,那么结束时间的小时数-开始时间的小时数后还得再减去1(借了1小时);同理,结束时间的分钟数-开始时间的分钟数还需要加上60(借了60分钟)。

如果结束时间的分钟数>开始时间的分钟数,直接相减即可。


P1421 小玉买文具

题目描述

班主任给小玉一个任务,到文具店里买尽量多的签字笔。已知一只签字笔的价格是 $1$ 元 $9$ 角,而班主任给小玉的钱是 $a$ 元 $b$ 角,小玉想知道,她最多能买多少只签字笔呢。

输入格式

输入只有一行两个整数,分别表示 $a$ 和 $b$。

输出格式

输出一行一个整数,表示小玉最多能买多少只签字笔。

样例输入 #1

1
10 3

样例输出 #1

1
5

数据规模与约定

对于全部的测试点,保证 $0 \leq a \leq 10^4$,$0 \leq b \leq 9$。

1
2
3
4
5
6
7
8
9
10
11
12
#include<bits/stdc++.h>
using namespace std;

int main()
{
int a, b;
double sum;
cin >> a >> b;
sum = a + b / 10.0;//强制b除以10为浮点数
cout << int(sum / 1.9);
return 0;
}

该题虽然输入的是整数,但几元几角还是属于浮点数,所以在过程中的每一步的计算结果都要保证是浮点数。

由于题求能买多少只笔,所以最后的输出结果记得要在强制转换成整数。


P5707 上学迟到(普及-)

题目描述

学校和 yyy 的家之间的距离为 $s$ 米,而 yyy 以 $v$ 米每分钟的速度匀速走向学校。

在上学的路上,yyy 还要额外花费 $10$ 分钟的时间进行垃圾分类。

学校要求必须在上午 $\textrm{8:00}$ 到达,请计算在不迟到的前提下,yyy 最晚能什么时候出门。

由于路途遥远,yyy 可能不得不提前一点出发,但是提前的时间不会超过一天。

输入格式

一行两个正整数 $s,v$,分别代表路程和速度。

输出格式

输出一个 $24$ 小时制下的时间,代表 yyy 最晚的出发时间。

输出格式为 $\texttt{HH:MM}$,分别代表该时间的时和分。必须输出两位,不足前面补 $0$。

样例输入 #1

1
100 99

样例输出 #1

1
07:48

提示

对于 $100%$ 的数据,$1 \le s,v \le 10^4$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;

int main()
{
double s, v, m;
int n, a, t, b;
cin >> s >> v;

n = 8 * 60 + 24 * 60;//两天总共的分钟数,从前一天的00:00到24:00,再到8:00
t = ceil(s / v) + 10;//上学路上花费的总时间
//ceil()很重要,向上取整,否则按C++逻辑会向下取整导致行走时间少。
n = n - t;//得出剩下的时间。

//判断是否在前一天,前一天这里指24:00之前
if (n >= 24 * 60) n = n - 24 * 60;//n -= 24 * 60; //不在前一天,就在24:00到8:00之间
//在前一天,由于n < 24 * 60,算出多少就是多少,而且题干明确说明不会提前一天,所以出发时间至少前一天的8:10之后
a = n / 60;//出发时间
b = n % 60;//出发分钟。

cout << setw(2) << setfill('0') << a << ":" << setw(2) << setfill('0') << b << endl;//自动补0
return 0;
}

该题偏难,前后做了一个小时。

我自己做只得了50分,之后看题解并对补0操作进行了优化。

本题的关键:如何处理出发时间在今天和出发时间在昨天的代码逻辑。

解释

  • setw(2):表示宽度为 2,也就是说输出的数字宽度至少为 2 位。如果输出的数字不足 2 位,C++ 会在左侧填充字符(默认是空格)。
  • **setw()**默认向右对齐,如果想要向左对齐,再前面加一个left即可,如cout << left << setw(8) << a << setw(8) << b;
  • setfill('0'):表示填充字符为 0。当数字的宽度小于 2 时,C++ 会使用 0 来填充。

【入门2】分支结构

P5709 苹果和虫子

题目描述

小 B 喜欢吃苹果。她现在有 $m$($1 \le m \le 100$)个苹果,吃完一个苹果需要花费 $t$($0 \le t \le 100$)分钟,吃完一个后立刻开始吃下一个。现在时间过去了 $s$($1 \le s \le 10000$)分钟,请问她还有几个完整的苹果?

输入格式

输入三个非负整数表示 $m, t, s$。

输出格式

输出一个整数表示答案。

样例输入 #1

1
50 10 200

样例输出 #1

1
30

提示

如果你出现了 RE,不如检查一下被零除?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<bits/stdc++.h>
using namespace std;

int main()
{
int m, t, s, rest;
cin >> m >> t >> s;
rest = m * t - s;//剩余的时间(精妙之处)
if (rest <= 0)
{
cout << 0;
}
else if (rest > 0)
{
cout << floor(rest / t);//下取整
}
return 0;
}

本题有点小坑,我以为是吃完整数个苹果后的花费时间为s,没想到也可以吃半个苹果后的花费时间为s。

所以就要考虑取整的问题了,用到了下取整函数floor()。

还有一个要考虑的点就是输入的m,s,t它不一定满足花费s时间后还剩下苹果。

这里我用了rest变量表示剩余的时间,再对rest变量进行分类讨论。


P5711 闰年判断

题目描述

输入一个年份,判断这一年是否是闰年,如果是输出 $1$,否则输出 $0$。

$1582$ 年以来,闰年的定义:

普通闰年:公历年份是 $4$ 的倍数,且不是 $100$ 的倍数的,为闰年(如 $2004$ 年、$2020$ 年等就是闰年)。

世纪闰年:公历年份是整百数的,必须是 $400$ 的倍数才是闰年(如 $1900$ 年不是闰年,$2000$ 年是闰年)。

输入格式

输入一个正整数 $n$,表示年份。

输出格式

输出一行。如果输入的年份是闰年则输出 $1$,否则输出 $0$。

提示

数据保证,$1582 \leq n \leq 2020$ 且年份为自然数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n;
cin >> n;

if ((n % 4 == 0 && n % 100 != 0)||n % 400 == 0)
{
cout << 1;
}
else
{
cout << 0;
}
return 0;
}

很经典的一道题。


P5715 三位数排序

题目描述

给出三个整数 $a,b,c(0\le a,b,c \le 100)$,要求把这三位整数从小到大排序。

输入格式

输入三个整数 $a,b,c$,以空格隔开。

输出格式

输出一行,三个整数,表示从小到大排序后的结果。

样例输入 #1

1
1 14 5

样例输出 #1

1
1 5 14

样例输入 #2

1
2 2 2

样例输出 #2

1
2 2 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<bits/stdc++.h>
using namespace std;

int main()
{
int a, b, c;
cin >> a >> b >> c;
if (a < b)
{
if (b < c) cout << a << " " << b << " " << c;
else if(a < c) cout << a << " " << c << " " << b;
else cout << c << " " << a << " " << b;
}
else if (a > b)
{
if (b > c) cout << c << " " << b << " " << a;
else if(c > a) cout << b << " " << a << " " << c;
else cout << b << " " << c << " " << a;
}
else
{
if(b > c) cout << c << " " << b << " " << a;
else cout << b << " " << a << " " << c;
}
return 0;
}

我自己做的,使用了if嵌套循环。但是看了题解后发现了更高级的手段。

1
2
3
4
5
6
7
8
9
10
11
12
#include<bits/stdc++.h>
using namespace std;

int main()
{
int a[3];
cin >> a[0] >> a[1] >> a[2];

sort(a,a+3);//sort()从小到大排序函数
cout << a[0] <<" "<< a[1] <<" "<< a[2];
return 0;
}

sort(Iterator first, Iterator last)参数

  • first:指向要排序区间的第一个元素的迭代器。
  • last:指向排序区间的末尾元素的后一个位置的迭代器。

不需要定义int a[4]:

不需要担心数组越界问题,因为 a + 3 是指向数组末尾的一个位置,但并不表示有效的元素。

Tip:可以使用reverse()函数对数组逆置

1
reverse(a,a+3);

P1085 不高兴的津津(重要)

题目描述

津津上初中了。妈妈认为津津应该更加用功学习,所以津津除了上学之外,还要参加妈妈为她报名的各科复习班。另外每周妈妈还会送她去学习朗诵、舞蹈和钢琴。但是津津如果一天上课超过八个小时就会不高兴,而且上得越久就会越不高兴。假设津津不会因为其它事不高兴,并且她的不高兴不会持续到第二天。请你帮忙检查一下津津下周的日程安排,看看下周她会不会不高兴;如果会的话,哪天最不高兴。

输入格式

输入包括 $7$ 行数据,分别表示周一到周日的日程安排。每行包括两个小于 $10$ 的非负整数,用空格隔开,分别表示津津在学校上课的时间和妈妈安排她上课的时间。

输出格式

一个数字。如果不会不高兴则输出 $0$,如果会则输出最不高兴的是周几(用 $1, 2, 3, 4, 5, 6, 7$ 分别表示周一,周二,周三,周四,周五,周六,周日)。如果有两天或两天以上不高兴的程度相当,则输出时间最靠前的一天。

样例输入 #1

1
2
3
4
5
6
7
5 3
6 2
7 2
5 3
5 4
0 4
0 6

样例输出 #1

1
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<bits/stdc++.h>
using namespace std;

int main()
{
int a, b, max = 8, q = 0;
for (int i = 1; i < 8; i++)
{
cin >> a >> b;
if ((a + b) > max)//神之一手
{
max = a + b;//锁住最大值
q = i;
}
}
cout << q;
return 0;
}

该题看着不难,却有几个条件限制住无法AC。

一开始我以为必须输入完7行后再进行输出判断,但后来才意识到输入区和输出区是独立开来的。

然后本题的神之一手就是max变量的使用,牢牢锁住最大值,让我受益颇丰!!!


P1909 买铅笔(重要)

题目描述

P 老师需要去商店买 $n$ 支铅笔作为小朋友们参加 NOIP 的礼物。她发现商店一共有 $3$ 种包装的铅笔,不同包装内的铅笔数量有可能不同,价格也有可能不同。为了公平起见,P 老师决定只买同一种包装的铅笔

商店不允许将铅笔的包装拆开,因此 P 老师可能需要购买超过 $n$ 支铅笔才够给小朋友们发礼物。

现在 P 老师想知道,在商店每种包装的数量都足够的情况下,要买够至少 $n$ 支铅笔最少需要花费多少钱。

输入格式

第一行包含一个正整数 $n$,表示需要的铅笔数量。

接下来三行,每行用 $2$ 个正整数描述一种包装的铅笔:其中第 $1$ 个整数表示这种包装内铅笔的数量,第 $2$ 个整数表示这种包装的价格。

保证所有的 $7$ 个数都是不超过 $10000$ 的正整数。

输出格式

$1$ 个整数,表示 P 老师最少需要花费的钱。

样例输入 #1

1
2
3
4
57
2 2
50 30
30 27

样例输出 #1

1
54

样例输入 #2

1
2
3
4
9998
128 233
128 2333
128 666

样例输出 #2

1
18407

样例输入 #3

1
2
3
4
9999
101 1111
1 9999
1111 9999

样例输出 #3

1
89991
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n, a, b, min = 100000000, sum = 0;
cin >> n;

for (int i = 0; i < 3; i++)
{
cin >> a >> b;
sum = ceil(double(n)/ a);//要买的铅笔盒数
if (sum * b < min)
{
min = sum * b;
}
}
cout << min;
return 0;
}

该题和上一题有异曲同工之处,上一题是锁定最大值max,该题是锁定最小值min。

有了思路很快就做出来了,但只有54分,下载了WA的样例测试了一下,发现当n<a时,ceil(n/a)= 0。导致最终输出的结果也是0。

查了Gpt得知,如代码中的 2488/6882 实际上是整数除法,因为两个数字都是整数。那么整数除法会返回一个整数结果,结果是 0(因为 2488 / 6882 小于 1,整数部分是 0)。然后, ceil(0)仍然是 0

如果想得到正确的结果,应该先将其中一个操作数转换为浮动类型(如 floatdouble)。

又学到了一个知识!


P1424 小鱼的航程(改进版)

题目描述

有一只小鱼,它平日每天游泳 $250$ 公里,周末休息(实行双休日),假设从周 $x$ 开始算起,过了 $n$ 天以后,小鱼一共累计游泳了多少公里呢?

输入格式

输入两个正整数 $x,n$,表示从周 $x$ 算起,经过 $n$ 天。

输出格式

输出一个整数,表示小鱼累计游泳了多少公里。

样例输入 #1

1
3 10

样例输出 #1

1
2000

提示

数据保证,$1\le x \le 7$,$1 \le n\le 10^6$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<bits/stdc++.h>
using namespace std;

int main()
{
int x;
long long n, sum = 0;
cin >> x >> n;

for (int i = 0; i < n; i++)
{
if ((x != 6) && (x != 7))//如果x不是周六或周日
{
sum += 250;//只有在周一二三四五才加
x++;
}
else if (x == 6)//如果x是周六
{
x++;
}
else//如果x是周日
{
x = 1;//x重置为1
}
}
cout << sum;
return 0;
}

该题看着好像需要讨论好多种情况,但实际只需要不停地累加

再就是题中明确说明n的值可能会是6位数,考虑到还需要乘250,使用long long类型(64位)。


P1888 三角函数

题目描述

输入一组勾股数 $a,b,c(a\neq b\neq c)$,用分数格式输出其较小锐角的正弦值。(要求约分。)

输入格式

一行,包含三个正整数,即勾股数 $a,b,c$(无大小顺序)。

输出格式

一行,包含一个分数,即较小锐角的正弦值

样例输入 #1

1
3 5 4

样例输出 #1

1
3/5

提示

数据保证:$a,b,c$ 为正整数且 $\in [1,10^9]$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<bits/stdc++.h>
using namespace std;

int gcd(int a, int b);
int main()
{
int a[3];
cin >> a[0] >> a[1] >> a[2];
sort(a, a + 3);
int max = a[2];
int min = a[0];
int t = gcd(max,min);
cout << min/t << "/" << max/t;

return 0;
}

int gcd(int a, int b)//求两个数的最大公约数
{
int temp;
while (b > 0)//辗转相除法
{
temp = a % b;
a = b;
b = temp;
}
return a;
}

该题有两个关键点:

1.输入的三个数为勾股数,这意味着三角形一定是直角三角形,求正弦值即短边/最长边。

2.约分需要找到分子和分母的最大公约数,而gcd()函数是GNU的私货,在Linux下的[编译器可用,那么就需要手写gcd()函数。


P5717 三角形分类(普及-)

题目描述

给出三条线段 $a,b,c$ 的长度,均是不大于 $10000$ 的正整数。打算把这三条线段拼成一个三角形,它可以是什么三角形呢?

  • 如果三条线段不能组成一个三角形,输出Not triangle
  • 如果是直角三角形,输出Right triangle
  • 如果是锐角三角形,输出Acute triangle
  • 如果是钝角三角形,输出Obtuse triangle
  • 如果是等腰三角形,输出Isosceles triangle
  • 如果是等边三角形,输出Equilateral triangle

如果这个三角形符合以上多个条件,请按以上顺序分别输出,并用换行符隔开。

输入格式

输入 3 个整数 $a$、$b$ 和 $c$。

输出格式

输出若干行判定字符串。

样例输入 #1

1
3 3 3

样例输出 #1

1
2
3
Acute triangle
Isosceles triangle
Equilateral triangle

样例输入 #2

1
3 4 5

样例输出 #2

1
Right triangle

样例输入 #3

1
6 10 6

样例输出 #3

1
2
Obtuse triangle
Isosceles triangle

样例输入 #4

1
1 14 5

样例输出 #4

1
Not triangle

提示

当两短边的平方和大于一长边的平方,说明是锐角三角形。

当两短边的平方和等于一长边的平方,说明是直角三角形。

当两短边的平方和小于一长边的平方,说明是钝角三角形。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<bits/stdc++.h>
using namespace std;

int main()
{
int m[3];
for (int i = 0; i < 3; i++)//输入三角形的三边
{
cin >> m[i];
}
sort(m, m + 3);//对三边从小到大排序
int a = m[0], b = m[1], c = m[2];//赋值给a,b,c

if ((a + b) > c && ((b - a) < c || (c - a) < b || (c - b) < a))//可以构成三角形
{
if (a * a + b * b == c * c) cout << "Right triangle" << endl;
if (a * a + b * b > c * c) cout << "Acute triangle" << endl;
if (a * a + b * b < c * c) cout << "Obtuse triangle" << endl;
if (a == b||b == c) cout << "Isosceles triangle" << endl;
if (a == c) cout << "Equilateral triangle" << endl;
}
else cout << "Not triangle";

return 0;
}

该题不难,主要就是用好数组和sort()排序函数。

if条件不可以出现a == b == c这种连等,会先计算 a == b,它会返回 truefalse(即 1 或 0),然后再将这个值与 c 进行比较。这样会导致逻辑错误。

if和else if是二选一的关系。


P1055 ISBN 号码(普及-)

题目描述

每一本正式出版的图书都有一个 ISBN 号码与之对应,ISBN 码包括 $9$ 位数字、$1$ 位识别码和 $3$ 位分隔符,其规定格式如 x-xxx-xxxxx-x,其中符号 - 就是分隔符(键盘上的减号),最后一位是识别码,例如 0-670-82162-4就是一个标准的 ISBN 码。ISBN 码的首位数字表示书籍的出版语言,例如 $0$ 代表英语;第一个分隔符 - 之后的三位数字代表出版社,例如 $670$ 代表维京出版社;第二个分隔符后的五位数字代表该书在该出版社的编号;最后一位为识别码。

识别码的计算方法如下:

首位数字乘以 $1$ 加上次位数字乘以 $2$ ……以此类推,用所得的结果 $ \bmod 11$,所得的余数即为识别码,如果余数为 $10$,则识别码为大写字母 $X$。例如 ISBN 号码 0-670-82162-4 中的识别码 $4$ 是这样得到的:对 067082162 这 $9$ 个数字,从左至右,分别乘以 $1,2,\dots,9$ 再求和,即 $0\times 1+6\times 2+……+2\times 9=158$,然后取 $158 \bmod 11$ 的结果 $4$ 作为识别码。

你的任务是编写程序判断输入的 ISBN 号码中识别码是否正确,如果正确,则仅输出 Right;如果错误,则输出你认为是正确的 ISBN 号码。

输入格式

一个字符序列,表示一本书的 ISBN 号码(保证输入符合 ISBN 号码的格式要求)。

输出格式

一行,假如输入的 ISBN 号码的识别码正确,那么输出 Right,否则,按照规定的格式,输出正确的 ISBN 号码(包括分隔符 -)。

样例输入 #1

1
0-670-82162-4

样例输出 #1

1
Right

样例输入 #2

1
0-670-82162-0

样例输出 #2

1
0-670-82162-4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<bits/stdc++.h>
using namespace std;

int main()
{
string s;
int a[10];
int r;
cin >> s;
a[0] = s[0] - '0';
a[1] = s[2] - '0';
a[2] = s[3] - '0';
a[3] = s[4] - '0';
a[4] = s[6] - '0';
a[5] = s[7] - '0';
a[6] = s[8] - '0';
a[7] = s[9] - '0';
a[8] = s[10] - '0';
a[9] = s[12] - '0';

//正确的识别码
r = (a[0] + a[1] * 2 + a[2] * 3 + a[3] * 4 + a[4] * 5 + a[5] * 6 + a[6] * 7 + a[7] * 8 + a[8] * 9) % 11;
//输入识别码不是X的情况
if (a[9] == r && s[12] != 'X') cout << "Right";
else if (a[9] != r && r != 10 && s[12] != 'X') cout << a[0] << "-" << a[1] << a[2] << a[3] << "-" << a[4] << a[5] << a[6] << a[7] << a[8] << "-" << r;
else if (a[9] != r && r == 10 && s[12] != 'X') cout << a[0] << "-" << a[1] << a[2] << a[3] << "-" << a[4] << a[5] << a[6] << a[7] << a[8] << "-" << "X";
//输入识别码是X的情况
else if (r == 10 && s[12] == 'X') cout << "Right";
else if(r != 10 && s[12] == 'X') cout << a[0] << "-" << a[1] << a[2] << a[3] << "-" << a[4] << a[5] << a[6] << a[7] << a[8] << "-" << r;

return 0;
}

该题看着好像只是数乘运算,但设计到整数和字符串在输入和输出时的处理方式。

例如输入的是一个字符串,验证识别码又需要使用整数乘法,而数字字符-'0’就可以转化为对应的整数。

最后就是对识别码是否为X要进行讨论。


【入门3】循环结构

P5721 数字直角三角形

题目描述

给出 $n$,请输出一个直角边长度是 $n$ 的数字直角三角形。所有数字都是 $2$ 位组成的,如果没有 $2$ 位则加上前导 $0$。

输入格式

输入一个正整数 $n$。

输出格式

输出如题目要求的数字直角三角形。

样例输入 #1

1
5

样例输出 #1

1
2
3
4
5
0102030405
06070809
101112
1314
15

提示

数据保证,$1\le n\le13$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n, a = 1;
cin >> n;
for (int i = n; i > 0; i--)
{
for (int m = i; m > 0; m--)
{
cout << setw(2) << setfill('0') << a;//不足两位自动补0
++a;
}
cout << endl;
}
return 0;
}

该题需要用到for循环嵌套,要注意条件中的变量。


P1980 计数问题

题目描述

试计算在区间 $1$ 到 $n$ 的所有整数中,数字 $x$($0\le x\le9$)共出现了多少次?例如,在 $1$ 到 $11$ 中,即在 $1,2,3,4,5,6,7,8,9,10,11$ 中,数字 $1$ 出现了 $4$ 次。

输入格式

$2$ 个整数 $n,x$,之间用一个空格隔开。

输出格式

$1$ 个整数,表示 $x$ 出现的次数。

样例输入 #1

1
11 1

样例输出 #1

1
4

提示

对于 $100%$ 的数据,$1\le n\le 10^6$,$0\le x \le 9$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n, x;
stringstream ss;
cin >> n >> x;
for (int i = 1; i < n+1; i++)
{
ss << i;
}
string s = ss.str();
cout << count(s.begin(), s.end(), x + '0') << endl;//整数加字符0可转化为对应的整数字符

return 0;
}

该题我一开始想的是将整数转化为字符串进行操作,但无奈不会写。

看了题解发现有一个答案很高级,且符合我的初心。

该解用到了字符串流stringstream,转化为字符串还要使用str()函数,最后使用count()函数查找特定字符的出现次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
int main()
{
long long n,i,x,b,c,t=0;
cin>>n>>x;//输入范围与要查的数字;
for(i=1;i<=n;i++)//一到n进行循环;
{
b=i;//为了不改变i的值,就把i赋值给一个数;
while(b!=0)//如果b不等于0,继续循环;
{
c=b%10;//求是否是x,是的话计数器加一;//低位
b=b/10;//求下一个数字是否为x;//高位
if(c==x) t++;计数器加一;
}
}
cout<<t<<endl;//输出计数器的数字;
return 0;//结束
}

最高赞解没有使用STL函数,而是很巧妙地对每一位整数进行检查。当然这种思路很难想,不如调用库函数简单快速。


P2669 金币

题目描述

国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天(第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚金币;之后四天(第七、八、九、十天),每天收到四枚金币……;这种工资发放模式会一直这样延续下去:当连续 $n$ 天每天收到 $n$ 枚金币后,骑士会在之后的连续 $n+1$ 天里,每天收到 $n+1$ 枚金币。

请计算在前 $k$ 天里,骑士一共获得了多少金币。

输入格式

一个正整数 $k$,表示发放金币的天数。

输出格式

一个正整数,即骑士收到的金币数。

样例输入 #1

1
6

样例输出 #1

1
14

样例输入 #2

1
1000

样例输出 #2

1
29820

提示

【样例 1 说明】

骑士第一天收到一枚金币;第二天和第三天,每天收到两枚金币;第四、五、六天,每天收到三枚金币。因此一共收到 $1+2+2+3+3+3=14$ 枚金币。

对于 $100%$ 的数据,$1\le k\le 10^4$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;

int main()
{
int k, now = 1, stage = 1, sum = 0;
cin >> k;
while (k)
{
sum = sum + now;
--stage;
if (stage == 0)//关键
{
++now;
stage = now;//重置stage的值
}
--k;
}
cout << sum;
return 0;
}

该题想了好久,还是没做出来。我一开始的思路就是:

循环K次,每一次循环加一次值,但问题是如何加相同的值呢,如何把某一时期的相同的值锁住呢?

题解中有一个答案很符合我的思路,利用好当前要加的金币数now和当前所处的阶段stage。


P5724 求极差 / 最大跨度值

题目描述

给出 $n$ 和 $n$ 个整数 $a_i$,求这 $n$ 个整数中的极差是什么。极差的意思是一组数中的最大值减去最小值的差。

输入格式

第一行输入一个正整数 $n$,表示整数个数。

第二行输入 $n$ 个整数 $a_1,a_2 \dots a_n$,以空格隔开。

输出格式

输出一个整数,表示这 $n$ 个整数的极差。

样例输入 #1

1
2
6
4 1 5 1 4 1

样例输出 #1

1
4

提示

数据保证,$1 \leq n\leq 100$,$0\le a_i \le 1000$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n;
cin >> n;
int* a = new int[n];//动态数组
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
sort(a, a + n);
cout << a[n - 1] - a[0];
delete[] a;//释放动态数组
return 0;
}

该题不难,使用sort()函数排序找到最大值和最小值即可。

我想记录的是动态数组的定义,这样节省内存且不易读取错误。


P1420 最长连号

题目描述

输入长度为 $n$ 的一个正整数序列,要求输出序列中最长连号的长度。

连号指在序列中,从小到大的连续自然数。

输入格式

第一行,一个整数 $n$。

第二行,$n$ 个整数 $a_i$,之间用空格隔开。

输出格式

一个数,最长连号的个数。

样例输入 #1

1
2
10
1 5 6 2 3 4 5 6 8 9

样例输出 #1

1
5

提示

对于 $100%$ 的数据,保证 $1 \leq n \leq 10^4$,$1 \leq a_i \leq 10^9$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n, flag = 1, max = 0;
cin >> n;
int* a = new int[n];
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
for (int j = 0; j < n; j++)
{
if (j < n && a[j] + 1 == a[j + 1])//防止数组越界
{
++flag;
}
else
{
flag = 1;//重置flag
}
if (flag > max)//锁住最大长度
{
max = flag;
}
}
cout << max;
delete[] a;

return 0;
}

该题似乎是一个动态规划问题,在上学期的算法课上见到过。

这里仅使用暴力解法。还是熟悉的遍历,锁住最大值。

要注意防止数组越界。


P1307 数字反转

题目描述

给定一个整数 $N$,请将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零(参见样例 2)。

输入格式

一个整数 $N$。

输出格式

一个整数,表示反转后的新数。

样例输入 #1

1
123

样例输出 #1

1
321

样例输入 #2

1
-380

样例输出 #2

1
-83

提示

$-1,000,000,000\leq N\leq 1,000,000,000 $。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
using namespace std;

int main()
{
int N;
cin >> N;
if (N >= 0)
{
string n = to_string(N);//整数——>字符串
reverse(n.begin(), n.end());//逆置
int m = stoi(n);//字符串——>整数
cout << m;

}
else
{
string n = to_string(N).substr(1);//去掉负号
reverse(n.begin(), n.end());
int m = stoi(n);
cout << "-" << m;
}
return 0;
}

该题比较简单,将输入的整数转换为字符串,再对字符串进行逆置,最后再转换回整数,顺便去除了首位的0。

需要注意的是当输入的数为负数时,转换为字符串后,要先将负号去掉才能正常地转换回整数。


P4956 北极旅行52周存钱

题目描述

在征服南极之后,Davor 开始了一项新的挑战。下一步是在西伯利亚、格林兰、挪威的北极圈远征。他将在 $2018$ 年 $12$ 月 $31$ 日开始出发,在这之前需要一共筹集 $n$ 元钱。他打算在每个星期一筹集 $x$ 元,星期二筹集 $x+k$ 元,……,星期日筹集 $x+6k$ 元,并连续筹集 $52$ 个星期。其中 $x,k$ 为正整数,并且满足 $1 \le x \le 100$。

现在请你帮忙计算 $x,k$ 为多少时,能刚好筹集 $n$ 元。

如果有多个答案,输出 $x$ 尽可能大,$k$ 尽可能小的。注意 $k$ 必须大于 $0$。

输入格式

The first line of input contains the integer N (1456 ≤ N ≤ 145600), the number from the task.

输出格式

The first line of output must contain the value of X (0 < X ≤ 100 ), and the second the value of
K (K > 0 ).

样例输入 #1

1
1456

样例输出 #1

1
2
1
1

样例输入 #2

1
6188

样例输出 #2

1
2
14
1

样例输入 #3

1
40404

样例输出 #3

1
2
99
4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n, x, k, sum = 0;
cin >> n;
for (int i = 1; i > 0; i++)
{
for (int j = 1; j < 101; j++)
{
sum = 364 * j + 1092 * i;
if (sum == n)
{
x = j;
k = i;
cout << x << endl << k;
return 0;
}
}
}
return 0;
}

该题看上去不简单,但还是蛮好做的。

题目明确规定52周恰好存到n元钱,那就意味着:52周的存钱总量的表达式可以固定下来。

最关键的一点是如果有多个答案,输出 x 尽可能大,k 尽可能小的,那就外部循环为k从1开始,内部循环为x从1到100遍历。当找到答案是就结束程序,此时的k一定是最小的。


P1089 津津的储蓄计划

题目描述

津津的零花钱一直都是自己管理。每个月的月初妈妈给津津 $300$ 元钱,津津会预算这个月的花销,并且总能做到实际花销和预算的相同。

为了让津津学习如何储蓄,妈妈提出,津津可以随时把整百的钱存在她那里,到了年末她会加上 $20%$ 还给津津。因此津津制定了一个储蓄计划:每个月的月初,在得到妈妈给的零花钱后,如果她预计到这个月的月末手中还会有多于 $100$ 元或恰好 $100$ 元,她就会把整百的钱存在妈妈那里,剩余的钱留在自己手中。

例如 $11$月初津津手中还有 $83$ 元,妈妈给了津津 $300$ 元。津津预计$11$月的花销是 $180$ 元,那么她就会在妈妈那里存 $200$ 元,自己留下 $183$ 元。到了 $11$ 月月末,津津手中会剩下 $3$ 元钱。

津津发现这个储蓄计划的主要风险是,存在妈妈那里的钱在年末之前不能取出。有可能在某个月的月初,津津手中的钱加上这个月妈妈给的钱,不够这个月的原定预算。如果出现这种情况,津津将不得不在这个月省吃俭用,压缩预算。

现在请你根据 $2004$ 年 $1$ 月到 $12$ 月每个月津津的预算,判断会不会出现这种情况。如果不会,计算到 $2004$ 年年末,妈妈将津津平常存的钱加上 $20%$ 还给津津之后,津津手中会有多少钱。

输入格式

$12$ 行数据,每行包含一个小于 $350$ 的非负整数,分别表示 $1$ 月到 $12$ 月津津的预算。

输出格式

一个整数。如果储蓄计划实施过程中出现某个月钱不够用的情况,输出 $-X$,$X$ 表示出现这种情况的第一个月;否则输出到 $2004$ 年年末津津手中会有多少钱。

注意,洛谷不需要进行文件输入输出,而是标准输入输出。

样例输入 #1

1
2
3
4
5
6
7
8
9
10
11
12
290
230
280
200
300
170
340
50
90
80
200
60

样例输出 #1

1
-7

样例输入 #2

1
2
3
4
5
6
7
8
9
10
11
12
290 
230
280
200
300
170
330
50
90
80
200
60

样例输出 #2

1
1580
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<bits/stdc++.h>
using namespace std;

int main()
{
int a[12], b[12];
int leave = 0, sum = 0;
for (int i = 0; i < 12; i++)//每个月的预算
{
cin >> a[i];
}
for (int j = 0; j < 12; j++)//判断每个月的钱是否够用
{
if ((300 + leave - a[j]) >= 0)//钱够用
{
b[j] = ((300 + leave - a[j]) / 100) * 100;//如果余下的钱大于等于100就能存钱,否则写入0
leave = leave + 300 - a[j] - b[j];
}
else//钱不够用
{
cout << "-" << ++j;
return 0;
}
}
for (int k = 0; k < 12; k++)//将存到妈妈那里的钱加起来
{
sum = sum + b[k];
}
cout << sum * 1.2 + leave;
return 0;
}

该题的题干很长,但依然不难。

需要注意的是:最后的总钱数=妈妈存的*1.2 + 津津剩余的


P1009 阶乘之和

题目描述

用高精度计算出 $S = 1! + 2! + 3! + \cdots + n!$($n \le 50$)。

其中 ! 表示阶乘,定义为 $n!=n\times (n-1)\times (n-2)\times \cdots \times 1$。例如,$5! = 5 \times 4 \times 3 \times 2 \times 1=120$。

输入格式

一个正整数 $n$。

输出格式

一个正整数 $S$,表示计算结果。

样例输入 #1

1
3

样例输出 #1

1
9

提示

【数据范围】

对于 $100 %$ 的数据,$1 \le n \le 50$。

【其他说明】

注,《深入浅出基础篇》中使用本题作为例题,但是其数据范围只有 $n \le 20$,使用书中的代码无法通过本题。

如果希望通过本题,请继续学习高精度的知识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<bits/stdc++.h>
using namespace std;

int main()
{
unsigned long long n, S = 0 ,q = 1, t = 0;
cin >> n;
for (int i = 1; i < n+1; i++)
{
for (int j = 1; j < i+1; j++)//计算每一次的阶乘
{
q = q * j;
}
S = S + q;
q = 1;//重置q为1
}
cout << S;
return 0;
}

我自己写的答案只能处理1到20的阶乘和,再往上就需要高精度计算了,但是我目前还不会。。。

等学了再写。


P5723 质数口袋

题目描述

小 A 有一个质数口袋,里面可以装各个质数。他从 $2$ 开始,依次判断各个自然数是不是质数,如果是质数就会把这个数字装入口袋。

口袋的负载量就是口袋里的所有数字之和。

但是口袋的承重量有限,装的质数的和不能超过 $L$。给出 $L$,请问口袋里能装下几个质数?将这些质数从小往大输出,然后输出最多能装下的质数的个数,数字之间用换行隔开。

输入格式

一行一个正整数 $L$。

输出格式

将这些质数从小往大输出,然后输出最多能装下的质数个数,所有数字之间有一空行。

样例输入 #1

1
100

样例输出 #1

1
2
3
4
5
6
7
8
9
10
2
3
5
7
11
13
17
19
23
9

样例输入 #2

1
5

样例输出 #2

1
2
3
2
3
2

样例输入 #3

1
11

样例输出 #3

1
2
3
4
2
3
5
3

提示

数据保证,$1 \le L \le {10}^5$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<bits/stdc++.h>
using namespace std;

bool isPrime(int x) //判断是否为质数
{
if (x < 2) return 0;
if (x == 2) return 1;
for (int i = 2; i <= sqrt(x); i++)
if (x % i == 0) return 0;
return 1;
}

int main()
{
int L, sum = 0, num = 0, flag = 0;
int a[100007];
cin >> L;
for (int i = 2; i < 100001; i++)//挨个判断是否为质数并存储
{
if (isPrime(i))
{
a[num] = i;
++num;
}
}
for (int j = 0; j < num; j++)
{
sum = sum + a[j];
if (sum <= L)
{
cout << a[j] << endl;
++flag;
}
}
cout << flag;
return 0;
}

啊,越来越觉得这些算法题本质上都是数学题了。

该题明确说明和L最大仅为5位数,即可以暴力求解。

先把从1到100000的所有的质数都找出来存放到数组中,然后依次加和并与L进行比较。

判断质数的方法:对于一个正整数 n,如果它能被 2根号n 之间的某个整数整除,则说明它不是质数,否则它就是质数。


P1075 质因数分解

题目描述

已知正整数 $n$ 是两个不同的质数的乘积,试求出两者中较大的那个质数。

输入格式

输入一个正整数 $n$。

输出格式

输出一个正整数 $p$,即较大的那个质数。

样例输入 #1

1
21

样例输出 #1

1
7

提示

$1 \le n\le 2\times 10^9$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n;
cin >> n;
for (int i = 2; i < sqrt(n); i++)//从2开始寻找
{
if (n % i == 0)
{
cout << n / i;
return 0;
}
}
return 0;
}

该题想了一会儿没有很好的思路,索性直接看了题解。

质因数分解,就是把一个合数看做几个质数相乘的积。如,2424的质因数分解为2∗2∗2∗3=24。

而题干明确说明输入的整数n一定是两个不同质数的乘积。

再如:

c=20

1∗20=20

2∗10=20

4∗5=20

5∗4=20

10∗2=20

20∗1=20

所以,不用依次寻找和比较哪个质因数更大。

由此分析,只要找到最小的质因数i,那么答案就是n/i。(找到后直接退出即可)


【入门4】数组

P5728 旗鼓相当的对手

题目描述

现有 $N$ 名同学参加了期末考试,并且获得了每名同学的信息:语文、数学、英语成绩(均为不超过 $150$ 的自然数)。如果某对学生 $\lang i,j\rang$ 的每一科成绩的分差都不大于 $5$,且总分分差不大于 $10$,那么这对学生就是“旗鼓相当的对手”。现在想知道这些同学中,有几对“旗鼓相当的对手”?同样一个人可能会和其他好几名同学结对。

输入格式

第一行一个正整数 $N$。

接下来 $N$ 行,每行三个整数,其中第 $i$ 行表示第 $i$ 名同学的语文、数学、英语成绩。最先读入的同学编号为 $1$。

输出格式

输出一个整数,表示“旗鼓相当的对手”的对数。

样例输入 #1

1
2
3
4
3
90 90 90
85 95 90
80 100 91

样例输出 #1

1
2

提示

数据保证,$2 \le N\le 1000$ 且每科成绩为不超过 $150$ 的自然数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n, flag = 0;
cin >> n;
int a[1000][3],sum[1000];//用二维数组存储每位同学的语数英成绩
for (int i = 0; i < n; i++)
{
cin >> a[i][0] >> a[i][1] >> a[i][2];//输入每位同学的语数英成绩
sum[i] = a[i][0] + a[i][1] + a[i][2];//计算总分
}
for (int j = 0; j < n; j++)//依次比较
{
int k = j + 1;
for (k; k <= n; k++)
{
if (abs(a[j][0] - a[k][0]) <= 5 &&
abs(a[j][1] - a[k][1]) <= 5 &&
abs(a[j][2] - a[k][2]) <= 5 &&
abs(sum[j] - sum[k]) <= 10)
{
++flag;
}
}
}
cout << flag;
return 0;
}

该题一开始我只想到了一维数组,但不知道怎么计算每位同学单科成绩的分差。看了题解才想起有二维数组这个东西。

那么就很简单了,二维数组的每一行即为该同学的语数英成绩。

比较的时候使用二重循环从第一行开始遍历。

注:abs()是计算绝对值的函数。


P5732 杨辉三角

题目描述

给出 $n(1\le n\le20)$,输出杨辉三角的前 $n$ 行。

如果你不知道什么是杨辉三角,可以观察样例找找规律。

输入格式

输出格式

样例输入 #1

1
6

样例输出 #1

1
2
3
4
5
6
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n;
cin >> n;
int a[20][20];
for (int i = 0; i < n; i++)//写入所有“1”
{
a[i][0] = 1;
a[i][i] = 1;
}
for (int i = 2; i < n; i++)
{
for (int j = 1; j < i; j++)
{
a[i][j] = a[i - 1][j - 1] + a[i - 1][j];//核心公式
}
}
for (int i = 0; i < n; i++)//依次输出
{
for (int j = 0; j < i + 1; j++)
{
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}

嗯,又是一个二维数组,二重for循环。


P1554 梦中的统计

题目背景

Bessie 处于半梦半醒的状态。过了一会儿,她意识到她在数数,不能入睡。

题目描述

Bessie 的大脑反应灵敏,仿佛真实地看到了她数过的一个又一个数。她开始注意每一个数码($0 \ldots 9$):每一个数码在计数的过程中出现过多少次?

给出两个整数 $M$ 和 $N$,求在序列 $[M, M + 1, M + 2, \ldots, N - 1, N]$ 中每一个数码出现了多少次。

输入格式

第 $1$ 行: 两个用空格分开的整数 $M$ 和 $N$。

输出格式

第 $1$ 行: 十个用空格分开的整数,分别表示数码 $0 \ldots 9$ 在序列中出现的次数。

样例输入 #1

1
129 137

样例输出 #1

1
1 10 2 9 1 1 1 1 0 1

提示

数据保证,$1 \leq M \leq N \leq 2 \times 10^9$,$N-M \leq 5 \times 10^5$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<bits/stdc++.h>
using namespace std;

int main()
{
int M, N;
cin >> M >> N;
stringstream ss;//字符串流对象
for (int i = M; i <= N; i++)
{
ss << i;
}
string s = ss.str();
for (int i = 0; i <= 9; i++)
{
cout << count(s.begin(), s.end(), i + '0') << " ";//数某个数字出现过的次数
}
return 0;
}

像这种匹配数字出现次数的题,我的第一反应就是使用字符串流stringstream,果然简单又高效。


P1319 压缩技术

题目描述

设某汉字由 $N \times N$ 的 $\texttt 0$ 和 $\texttt 1$ 的点阵图案组成。

我们依照以下规则生成压缩码。连续一组数值:从汉字点阵图案的第一行第一个符号开始计算,按书写顺序从左到右,由上至下。第一个数表示连续有几个 $\texttt 0$,第二个数表示接下来连续有几个 $\texttt 1$,第三个数再接下来连续有几个 $\texttt 0$,第四个数接着连续几个 $\texttt 1$,以此类推……

例如: 以下汉字点阵图案:

1
2
3
4
5
6
7
0001000
0001000
0001111
0001000
0001000
0001000
1111111

对应的压缩码是: $\texttt {7 3 1 6 1 6 4 3 1 6 1 6 1 3 7}$ (第一个数是 $N$ ,其余各位表示交替表示0和1 的个数,压缩码保证 $N \times N=$ 交替的各位数之和)

输入格式

数据输入一行,由空格隔开的若干个整数,表示压缩码。

其中,压缩码的第一个数字就是 $N$,表示这个点阵应当是 $N\times N$ 的大小。

接下来的若干个数字,含义如题目描述所述。

输出格式

输出一个 $N\times N$ 的 01 矩阵,表示最后的汉字点阵图(点阵符号之间不留空格)。

样例输入 #1

1
7 3 1 6 1 6 4 3 1 6 1 6 1 3 7

样例输出 #1

1
2
3
4
5
6
7
0001000
0001000
0001111
0001000
0001000
0001000
1111111

提示

样例解释

数据范围

数据保证,$3\leq N\leq 200$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;

int main()
{
int N,now;
cin >> N;
int flag = N * N;//用来计数
int t = 0;
while (flag)
{
cin >> now;//输入当前要输入t的个数
for (int j = 0; j < now; j++)
{
cout << t;
--flag;//每输出一个数字,flag减一
if (flag % N == 0) cout << endl;//每行达到N个数字时,换行。
}
if (t == 0) t = 1;//0、1切换
else t = 0;
}
return 0;
}

该题让输出一个压缩矩阵,能够想到每次循环只输出一个数字,题就瞬间简单很多了。


P1614 爱与愁的心痛

题目描述

最近有 $n$ 个不爽的事,每句话都有一个正整数刺痛值(心理承受力极差)。爱与愁大神想知道连续 $m$ 个刺痛值的和的最小值是多少,但是由于业务繁忙,爱与愁大神只好请你编个程序告诉他。

输入格式

第一行有两个用空格隔开的整数,分别代表 $n$ 和 $m$。

第 $2$ 到第 $(n + 1)$ 行,每行一个整数,第 $(i + 1)$ 行的整数 $a_i$ 代表第 $i$ 件事的刺痛值 $a_i$。

输出格式

输出一行一个整数,表示连续 $m$ 个刺痛值的和的最小值是多少。

样例输入 #1

1
2
3
4
5
6
7
8
9
8 3
1
4
7
3
1
2
4
3

样例输出 #1

1
6

提示

  • 对于 $30%$ 的数据,保证 $n \leq 20$。
  • 对于 $60%$ 的数据,保证 $n \leq 100$。
  • 对于 $90%$ 的数据,保证 $n \leq 10^3$。
  • 对于 $100%$ 的数据,保证 $0 \leq m \leq n \leq 3 \times 10^3$,$1 \leq a_i \leq 100$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n, m, sum = 0;
cin >> n >> m;
int* a = new int[n];//存放输入的正整数
int* b = new int[n - m + 1];//存放连续三个正整数的和
for (int i = 0; i < n; i++)//输入n个正整数刺痛值
{
cin >> a[i];
}
for (int i = 0; i < n - m + 1; i++)//从第一个整数开始连续数3个
{
for (int j = 0; j < m; j++)//防止i+j越界
{
sum = sum + a[i + j];
}
b[i] = sum;
sum = 0;
}
sort(b, b + (n - m + 1));
cout << b[0];
delete[] a; // 释放动态分配的内存
delete[] b;
return 0;
}

该题还是熟悉的从一行开始连续数几个值,求和求最小值。需要用到二重for循环。

最关键的是数组b的大小要设置为n-m+1。


【入门5】字符串

P5733 字母大小写转换

题目描述

大家都知道一些办公软件有自动将字母转换为大写的功能。输入一个长度不超过 $100$ 且不包括空格的字符串。要求将该字符串中的所有小写字母变成大写字母并输出。

输入格式

输入一行,一个字符串。

输出格式

输出一个字符串,即将原字符串中的所有小写字母转化为大写字母。

样例输入 #1

1
Luogu4!

样例输出 #1

1
LUOGU4!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<bits/stdc++.h>
using namespace std;

int main()
{
string s;
cin >> s;
for (int i = 0; i < s.size(); i++)
{
s[i] = toupper(s[i]);
}
cout << s;
return 0;
}

该题将小写字母转换为大写字母,我直接调用了toupper函数。

此外,字符串在计算机中是以字符数组的形式来储存,所以可以使用数组。

toupper函数原型(小写字母转大写字母)

1
2
3
4
5
6
int toupper(int c)  
{
if ((c >= 'a') && (c <= 'z'))
return c + ('A' - 'a');
return c;
}

tolower函数原型(大写字母转小写字母)

1
2
3
4
5
6
int tolower(int c)  
{
if ((c >= 'A') && (c <= 'Z'))
return c + ('a' - 'A');
return c;
}

它们有一个优点:只会修改英文字母。

注意,这两个函数只能一次修改一个字符。


P1914 小书童——凯撒密码

题目背景

某蒟蒻迷上了 “小书童”,有一天登陆时忘记密码了(他没绑定邮箱 or 手机),于是便把问题抛给了神犇你。

题目描述

蒟蒻虽然忘记密码,但他还记得密码是由一个字符串组成。密码是由原文字符串(由不超过 50 个小写字母组成)中每个字母向后移动 $n$ 位形成的。z 的下一个字母是 a,如此循环。他现在找到了移动前的原文字符串及 $n$,请你求出密码。

输入格式

第一行:$n$。第二行:未移动前的一串字母。

输出格式

一行,是此蒟蒻的密码。

样例输入 #1

1
2
1
qwe

样例输出 #1

1
rxf

提示

字符串长度 $\le 50$,$1 \leq n \leq 26$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n;
cin >> n;
string s;
cin >> s;
for (int i = 0; i < s.size(); i++)
{
// 计算移动后的字符
s[i] = 'a' + (s[i] - 'a' + n) % 26;//核心
cout << s[i];
}
return 0;
}

(s[i] - ‘a’)是s[i]相对于’a’的的偏移量,在此基础上进行移位。

最后取模26保证偏移值始终在0-25。


P3741 小果的键盘

题目背景

小果有一个只有两个键的键盘。

题目描述

一天,她打出了一个只有这两个字符的字符串。当这个字符串里含有 VK 这个字符串的时候,小果就特别喜欢这个字符串。所以,她想改变至多一个字符(或者不做任何改变)来最大化这个字符串内 VK 出现的次数。给出原来的字符串,请计算她最多能使这个字符串内出现多少次 VK(只有当 VK 正好相邻时,我们认为出现了 VK。)

输入格式

第一行给出一个数字 $n$,代表字符串的长度。

第二行给出一个字符串 $s$。

输出格式

第一行输出一个整数代表所求答案。

样例输入 #1

1
2
2
VK

样例输出 #1

1
1

样例输入 #2

1
2
2
VV

样例输出 #2

1
1

样例输入 #3

1
2
1
V

样例输出 #3

1
0

样例输入 #4

1
2
20
VKKKKKKKKKVVVVVVVVVK

样例输出 #4

1
3

样例输入 #5

1
2
4
KVKV

样例输出 #5

1
1

提示

对于 $100%$ 的数据,$1\le n\le 100$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n,cnt = 0;
cin >> n;
string s;
cin >> s;
for (int i = 0; i < n; i++)
{ //逐对查找VK,cnt加一,将VK变为XX
if (s[i] == 'V' && s[i + 1] == 'K'&& i + 1 < n)//注意数组不要越界
{
++cnt;
s[i] = 'X';
s[i + 1] = 'X';
}
}
for (int i = 0; i < n; i++)
{ //第二次逐对查找VV或KK,找到一个cnt立即加一并输出,终止程序
if ((s[i] == 'V' && s[i + 1] == 'V') || (s[i] == 'K' && s[i + 1] == 'K'))
{
++cnt;
cout << cnt;
return 0;
}
}
cout << cnt;
return 0;
}

无非就是VK、KV、VV、KK四种情况,抛去KV不可用,分别讨论即可。


【入门6】函数与结构体

P5739 计算阶乘

题目描述

求 $n!$,也就是 $1\times2\times3\dots\times n$。

挑战:尝试**不使用循环语句(for、while)**完成这个任务。

输入格式

第一行输入一个正整数 $n$。

输出格式

输出一个正整数,表示 $n!$。

样例输入 #1

1
3

样例输出 #1

1
6

提示

数据保证,$1 \leq n\le12$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<bits/stdc++.h>
using namespace std;

int func(int x)//递归函数
{
if (x == 0) return 1;//递归的最后一步
return x * func(x - 1);
}
int main()
{
int n;
cin >> n;
cout << func(n);//递归
return 0;
}

由于该题要求尽量避免使用循环语句,所以使用递归算法

也算是第一次敲递归函数,hhh挺有纪念意义的。


P1304 哥德巴赫猜想

题目描述

输入一个偶数 $N$,验证 $4\sim N$ 所有偶数是否符合哥德巴赫猜想:任一大于 $2$ 的偶数都可写成两个质数之和。如果一个数不止一种分法,则输出第一个加数相比其他分法最小的方案。例如 $10$,$10=3+7=5+5$,则 $10=5+5$ 是错误答案。

输入格式

第一行输入一个正偶数 $N$

输出格式

输出 $\dfrac{N-2}{2}$ 行。对于第 $i$ 行:

首先先输出正偶数 $2i+2$,然后输出等号,再输出加和为 $2i+2$ 且第一个加数最小的两个质数,以加号隔开。

样例输入 #1

1
10

样例输出 #1

1
2
3
4
4=2+2
6=3+3
8=3+5
10=3+7

提示

数据保证,$ 4 \leq N\leq10000$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<bits/stdc++.h>
using namespace std;

bool isPrime(int x) // 判断是否为质数
{
if (x < 2) return 0;
for (int i = 2; i <= sqrt(x); i++)
{
if (x % i == 0) return 0;
}
return 1;
}

int main()
{
int N;
cin >> N; // 输入正偶数 N

// 遍历从 4 到 N 的所有偶数
for (int m = 4; m <= N; m += 2)
{
// 遍历所有可能的质数对
for (int i = 2; i <= m / 2; i++)//核心
{ //i从小到大
if (isPrime(i) && isPrime(m - i)) // 检查 i 和 m-i 是否都是质数
{
cout << m << "=" << i << "+" << m - i << endl; // 输出结果
break; // 找到第一个满足条件的质数对后退出循环
}
}
}
return 0;
}

该题问了AI,它真的好强。。。

for (int i = 2; i <= m / 2; i++)中的i <= m / 2是因为前面的质数要小于等于后面的质数,如i <= m - i 。


P5744 培训

题目描述

某培训机构的学员有如下信息:

  • 姓名(字符串)
  • 年龄(周岁,整数)
  • 去年 NOIP 成绩(整数,且保证是 $5$ 的倍数)

经过为期一年的培训,所有同学的成绩都有所提高,提升了 $20%$(当然 NOIP 满分是 $600$ 分,不能超过这个得分)。

输入学员信息,请设计一个结构体储存这些学生信息,并设计一个函数模拟培训过程,其参数是这样的结构体类型,返回同样的结构体类型,并输出学员信息。

输入格式

第一行输入一个正整数 $n$,表示学员个数。

第二行开始往下 $n$ 行。每行首先是一个字符串表示学员姓名,再是一个整数表示学员年龄,再是一个整数为去年 NOIP 成绩。

输出格式

输出 $n$ 行,每行首先输出一个字符串表示学生姓名,再往后两个整数,表示经过一年的培训后学员的年龄和他们今年的 NOIP 成绩。以空格隔开。

样例输入 #1

1
2
3
4
3
kkksc03 24 0
chen_zhe 14 400
nzhtl1477 18 590

样例输出 #1

1
2
3
kkksc03 25 0
chen_zhe 15 480
nzhtl1477 19 600

提示

数据保证,$1 \leq n \leq 5$。年龄为 $0 \sim 100$(含 $0$ 与 $100$)的整数。成绩为 $0 \sim 600$(含 $0$ 与 $600$)的 $5$ 的整倍数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<bits/stdc++.h>
using namespace std;

struct Student//结构体定义
{
string name;
int age;
int grade;
};
Student Training(Student s)//模拟函数
{
s.age += 1;
s.grade = min(600,s.grade + s.grade * 20 / 100);//注意括号内的数据类型要一致,都为整数
return s;
}
int main()
{
int n;
cin >> n;
Student* stu = new Student[n];//动态分配内存 结构体数组
for (int i = 0; i < n; i++)//输入n个学生的信息
{
cin >> stu[i].name >> stu[i].age >> stu[i].grade;
}
for (int i = 0; i < n; i++)
{
stu[i] = Training(stu[i]);
cout << stu[i].name << " " << stu[i].age << " " << stu[i].grade << endl;
}
return 0;
}

该题要使用结构体,也算是第一次真正敲代码。


【算法1-3】暴力枚举

P1706 全排列问题

题目描述

按照字典序输出自然数 $1$ 到 $n$ 所有不重复的排列,即 $n$ 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式

一个整数 $n$。

输出格式

由 $1 \sim n$ 组成的所有不重复的数字序列,每行一个序列。

每个数字保留 $5$ 个场宽。

输入 #1

1
3

输出 #1

1
2
3
4
5
6
1    2    3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

说明/提示

$1 \leq n \leq 9$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<bits/stdc++.h>
using namespace std;

int a[10], n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
a[i] = i;
}
do
{
for (int i = 1; i <= n; i++)
{
cout << setw(5) << a[i];
}
cout << endl;
} while (next_permutation(a + 1, a + n + 1));

return 0;
}

全排列问题,使用next_permutation()函数。

next_permutation 的作用是:

  • 将序列更改为字典序中的下一个排列
  • 如果当前序列已经是字典序中的最后一个排列,则将其重置为第一个排列,并返回 false

注意场宽为5使用setw()函数。


【算法1-4】递推与递归

P1255 数楼梯

题目描述

楼梯有 $N$ 阶,上楼可以一步上一阶,也可以一步上二阶。

编一个程序,计算共有多少种不同的走法。

输入格式

一个数字,楼梯数。

输出格式

输出走的方式总数。

输入 #1

1
4

输出 #1

1
5

说明/提示

  • 对于 $60%$ 的数据,$N \leq 50$;
  • 对于 $100%$ 的数据,$1 \le N \leq 5000$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;

int main()
{
int a[27],n;
cin >> n;
a[1] = 1;//初始条件
a[2] = 2;
for (int i = 3; i <= n; i++)
{
a[i] = a[i - 1] + a[i - 2];//递推公式
}
cout << a[n];

return 0;
}

还不会高精度,这里只可以测试20以内的数。

主要是为了学习递推的思想。


【算法1-5】贪心

P2240 部分背包问题

题目描述

阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 $N(N \le 100)$ 堆金币,第 $i$ 堆金币的总重量和总价值分别是 $m_i,v_i(1\le m_i,v_i \le 100)$。阿里巴巴有一个承重量为 $T(T \le 1000)$ 的背包,但并不一定有办法将全部的金币都装进去。他想装走尽可能多价值的金币。所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?

输入格式

第一行两个整数 $N,T$。

接下来 $N$ 行,每行两个整数 $m_i,v_i$。

输出格式

一个实数表示答案,输出两位小数

输入 #1

1
2
3
4
5
4 50
10 60
20 100
30 120
15 45

输出 #1

1
240.00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;

struct coin {
int m, v;//金币堆的重量和价值
}a[110];

bool cmp(coin x, coin y) {
return x.v * y.m > y.v * x.m;//判断单价,避免浮点数运算
}
int main()
{
int N, T, i;
double ans = 0;
cin >> N >> T;
for ( i = 0; i < N; i++)
cin >> a[i].m >> a[i].v;
sort(a, a + N, cmp);//对单价排序
for ( i = 0; i < N; i++)
{
if (a[i].m > T) break;
T -= a[i].m;//背包的剩余重量
ans += a[i].v;
}

if (i < N) ans += 1.0 * T / a[i].m * a[i].v;//剩余空间装下部分金币(切割)
cout << setprecision(2)<< fixed <<ans;//保证答案是两位小数

return 0;
}

本题使用贪心算法,优先放入单位重量价值更高的金币,放不下时再切割当前价值最大的金币放入背包。

自定义cmp函数

  • 如果 x.v * y.m > y.v * x.m,说明堆 x 的单位价值高于堆 y 的单位价值。
  • 因此,cmp 函数会返回 true,表示堆 x 应该排在堆 y 的前面。

要注意变量i的使用,i 的值决定了当前正在处理哪一堆金币。


【算法1-6】二分查找与二分答案

P2249 二分查找

题目描述

输入 $n$ 个不超过 $10^9$ 的单调不减的(就是后面的数字不小于前面的数字)非负整数 $a_1,a_2,\dots,a_{n}$,然后进行 $m$ 次询问。对于每次询问,给出一个整数 $q$,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 $-1$ 。

输入格式

第一行 $2$ 个整数 $n$ 和 $m$,表示数字个数和询问次数。

第二行 $n$ 个整数,表示这些待查询的数字。

第三行 $m$ 个整数,表示询问这些数字的编号,从 $1$ 开始编号。

输出格式

输出一行,$m$ 个整数,以空格隔开,表示答案。

输入 #1

1
2
3
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6

输出 #1

1
1 2 -1

说明/提示

数据保证,$1 \leq n \leq 10^6$,$0 \leq a_i,q \leq 10^9$,$1 \leq m \leq 10^5$

本题输入输出量较大,请使用较快的 IO 方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<bits/stdc++.h>
using namespace std;

int n, m, q, a[1000010];
int find(int x) {
int l = 1, r = n;
while (l <= r)
{
int mid = (l + r) / 2;//找到中间值
if (a[mid] == x) return mid;//刚好是中间值
else if (a[mid] > x) r = mid - 1;//缩小半区
else l = mid + 1;
}
return -1;//找不到
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];//给定n个数
for (int i = 0; i < m; i++)//查询m次
{
cin >> q;//要查找的数
cout << find(q)<<" ";
}

return 0;
}
1
2
3
4
5
6
7
输入:
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6

输出:
1 3 -1

该解答并不完美,在找3的时候没有找到最小下标的3。该程序适用于序列中的数字各不重复的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<bits/stdc++.h>
using namespace std;

int n, m, q, a[1000010];
int find(int x) {
int l = 1, r = n;
int result = -1;
while (l <= r)
{
int mid = (r + l) / 2;//找到中间值
if (a[mid] == x)
{
result = mid;//刚好是中间值
r = mid - 1;//继续向左查找
}
else if (a[mid] > x) r = mid - 1;//缩小半区
else l = mid + 1;
}
return result;//找不到
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];//给定n个数
for (int i = 0; i < m; i++)//查询m次
{
cin >> q;//要查找的数
cout << find(q)<<" ";
}

return 0;
}

该解答使用变量result保存当前查找到的结果,然后再继续向左查找,直到找到最小编号。


【算法1-7】搜索

四阶数独

给出一个4*4的格子,每个格子只能填写1到4的整数,要求每行、每列和四等分更小的正方形部分都刚好由1到4组成。

请问一共有多少种合法的填写方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<bits/stdc++.h>
using namespace std;
#define size 5

int a[size * size], n = 4 * 4, ans = 0;
int b1[size][5], b2[size][5], b3[size][5];

void dfs(int x) {
if (x > n)//所有空都被填满
{
ans++;
//for (int i = 1; i <= n; i++)//输出四阶数独
//{
// cout << a[i];
// if (i % 4 == 0) cout << endl;
//}
return;
}

int row = (x - 1) / 4 + 1;//横行编号
int col = (x - 1) % 4 + 1;//竖排编号
int block = (row - 1) / 2 * 2 + (col - 1) / 2 + 1;//小块编号
for (int i = 1; i <= 4; i++)
{
//核心代码
if (b1[row][i] == 0 && b2[col][i] == 0 && b3[block][i] == 0)
{
a[x] = i;//放入数字
b1[row][i] = 1; b2[col][i] = 1; b3[block][i] = 1;//占位
dfs(x + 1);//下一层递归
b1[row][i] = 0; b2[col][i] = 0; b3[block][i] = 0;//逐层退出时,取消占位,以便枚举其他数字
}
}

}
int main()
{
dfs(1);
cout << ans;

return 0;
}

本题使用了回溯算法,常用深度优先搜索(DFS)来实现。挺难的,感觉需要记住这个模板。

答案:288种。

1. 代码的作用

这段代码的作用是:

  1. 遍历数字 14,尝试将每个数字填入当前格子。
  2. 检查填入的数字是否满足数独的规则(即在同一行、同一列、同一小方块中没有重复)。
  3. 如果满足规则,则递归地填充下一个格子。
  4. 如果递归完成后没有找到解,则回溯(撤销当前格子的填充),并尝试下一个数字。

2. 代码逐行解释

(1) for (int i = 1; i <= 4; i++)

  • 这是一个循环,遍历数字 14
  • 对于每个数字 i,尝试将其填入当前格子。

(2) if (b1[row][i] == 0 && b2[col][i] == 0 && b3[block][i] == 0)

  • 这是一个条件判断,检查数字 i 是否可以填入当前格子。
  • b1[row][i] == 0:检查数字 i 是否在当前行 row 中未被使用。
  • b2[col][i] == 0:检查数字 i 是否在当前列 col 中未被使用。
  • b3[block][i] == 0:检查数字 i 是否在当前小方块 block 中未被使用。
  • 如果三个条件都满足,说明数字 i 可以填入当前格子。

(3) a[x] = i;

  • 将数字 i 填入当前格子 a[x]

(4) b1[row][i] = 1; b2[col][i] = 1; b3[block][i] = 1;

  • 标记数字 i 在当前行、当前列、当前小方块中已被使用。
  • b1[row][i] = 1:标记数字 i 在当前行 row 中已被使用。
  • b2[col][i] = 1:标记数字 i 在当前列 col 中已被使用。
  • b3[block][i] = 1:标记数字 i 在当前小方块 block 中已被使用。

(5) dfs(x + 1);

  • 递归调用 dfs 函数,填充下一个格子 x + 1
  • 如果递归完成后找到解,则继续回溯;如果未找到解,则尝试下一个数字。

(6) b1[row][i] = 0; b2[col][i] = 0; b3[block][i] = 0;

  • 回溯操作:撤销当前格子的填充,并取消数字 i 的标记。
  • b1[row][i] = 0:取消数字 i 在当前行 row 中的标记。
  • b2[col][i] = 0:取消数字 i 在当前列 col 中的标记。
  • b3[block][i] = 0:取消数字 i 在当前小方块 block 中的标记。

3. 举例说明

假设当前格子 x 的行 row = 1,列 col = 1,小方块 block = 1

(1) 尝试填入数字 1

  • 检查 b1[1][1]b2[1][1]b3[1][1] 是否都为 0
  • 如果都为 0,则:
    • a[x] 赋值为 1
    • 标记 b1[1][1] = 1b2[1][1] = 1b3[1][1] = 1
    • 递归调用 dfs(x + 1),填充下一个格子。
    • 递归完成后,回溯:b1[1][1] = 0b2[1][1] = 0b3[1][1] = 0

(2) 尝试填入数字 2

  • 检查 b1[1][2]b2[1][2]b3[1][2] 是否都为 0
  • 如果都为 0,则:
    • a[x] 赋值为 2
    • 标记 b1[1][2] = 1b2[1][2] = 1b3[1][2] = 1
    • 递归调用 dfs(x + 1),填充下一个格子。
    • 递归完成后,回溯:b1[1][2] = 0b2[1][2] = 0b3[1][2] = 0

(3) 尝试填入数字 34

  • 类似地,尝试填入数字 34,并递归填充下一个格子。

P1219 八皇后

题目描述

一个如下的 $6 \times 6$ 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 $2\ 4\ 6\ 1\ 3\ 5$ 来描述,第 $i$ 个数字表示在第 $i$ 行的相应位置有一个棋子,如下:

行号 $1\ 2\ 3\ 4\ 5\ 6$

列号 $2\ 4\ 6\ 1\ 3\ 5$

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 $3$ 个解。最后一行是解的总个数。

输入格式

一行一个正整数 $n$,表示棋盘是 $n \times n$ 大小的。

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。

输入 #1

1
6

输出 #1

1
2
3
4
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

说明/提示

【数据范围】
对于 $100%$ 的数据,$6 \le n \le 13$。

题目翻译来自NOCOW。

USACO Training Section 1.5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h>
using namespace std;
#define maxn 100

int a[maxn], n, ans = 0;
int b1[maxn], b2[maxn], b3[maxn];//分别记录皇后的y轴,主对角线,副对角线是否被占用(后两个不会)

//默认是从第一行往下逐个放置
void dfs(int x) {
if (x > n)//所有皇后放置完毕
{
ans++;
if (ans <= 3)//输出前三种答案
{
for (int i = 1; i <= n; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
}
for (int i = 1; i <= n; i++)//i表示第几列
{
if (b1[i] == 0 && b2[x + i] == 0 && b3[x - i + 15] == 0)
{
a[x] = i;//记录放置位置
b1[i] = 1; b2[x + i] = 1; b3[x - i + 15] = 1;//占位
dfs(x + 1);//下一层递归
b1[i] = 0; b2[x + i] = 0; b3[x - i + 15] = 0;//取消占位
}
}

}
int main()
{
cin >> n;
dfs(1);
cout << ans;

return 0;
}

1. 代码功能

  • 输入一个整数 n,表示棋盘的大小(n x n)和皇后的数量。
  • 使用 DFS 枚举所有可能的皇后放置方式,确保皇后之间不会互相攻击。
  • 输出前三种解的具体放置方式,并输出解的总数。

2. 代码逻辑

(1) 变量定义

  • a[maxn]:记录每行皇后所在的列位置。a[x] = i 表示第 x 行的皇后放在第 i 列。
  • b1[maxn]:记录每一列是否被占用。b1[i] = 1 表示第 i 列已被占用。
  • b2[maxn]:记录每条主对角线是否被占用。b2[x + i] = 1 表示主对角线 x + i 已被占用。
  • b3[maxn]:记录每条副对角线是否被占用。b3[x - i + 15] = 1 表示副对角线 x - i + 15 已被占用。
  • ans:记录解的总数。

(2) DFS 函数

  • dfs(x):递归函数,用于在第 x 行放置皇后。
    • 如果 x > n,说明所有皇后都已放置完毕,找到一个解,ans++
    • 如果 ans <= 3,输出当前解的具体放置方式。
    • 否则,遍历每一列 i,尝试将皇后放在第 x 行第 i 列。
      • 检查是否满足条件:第 i 列、主对角线 x + i、副对角线 x - i + 15 都未被占用。
      • 如果满足条件,则放置皇后,并递归放置下一行的皇后。
      • 递归完成后,回溯(撤销当前放置),尝试其他列。

(3) 主函数

  • 输入 n,表示棋盘大小和皇后数量。
  • 调用 dfs(1),从第 1 行开始放置皇后。
  • 输出解的总数 ans

3. 关键点解释

(1) 对角线的表示

  • 主对角线x + i 是主对角线的唯一标识。例如,x = 2i = 3,则主对角线为 2 + 3 = 5
  • 副对角线x - i + 15 是副对角线的唯一标识。+15 是为了避免负数下标。例如,x = 2i = 3,则副对角线为 2 - 3 + 15 = 14

(2) 回溯

  • 在 DFS 中,每次递归完成后,需要撤销当前放置的皇后,以便尝试其他可能的放置方式。

  • 回溯通过以下代码实现:

    1
    b1[i] = 0; b2[x + i] = 0; b3[x - i + 15] = 0;

(3) 输出前三种解

  • 当找到一个解时,如果 ans <= 3,则输出当前解的具体放置方式。

  • 例如,n = 4 时,输出可能为:

    1
    2
    2 4 1 3
    3 1 4 2

【数据结构1-1】线性表

P3156 询问学号

题目描述

有 $n(n \le 2 \times 10^6)$ 名同学陆陆续续进入教室。我们知道每名同学的学号(在 $1$ 到 $10^9$ 之间),按进教室的顺序给出。上课了,老师想知道第 $i$ 个进入教室的同学的学号是什么(最先进入教室的同学 $i=1$),询问次数不超过 $10^5$ 次。

输入格式

第一行 $2$ 个整数 $n$ 和 $m$,表示学生个数和询问次数。

第二行 $n$ 个整数,表示按顺序进入教室的学号。

第三行 $m$ 个整数,表示询问第几个进入教室的同学。

输出格式

输出 $m$ 个整数表示答案,用换行隔开。

输入 #1

1
2
3
10 3
1 9 2 60 8 17 11 4 5 14
1 5 9

输出 #1

1
2
3
1
8
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<bits/stdc++.h>
using namespace std;

int n, m, tmp;
int main()
{
vector<int> stu;//建立一个一维的可变数组
cin >> n >> m;
for (int i = 0; i < n; i++)
{
cin >> tmp;
stu.push_back(tmp);//将学生的学号依次加入到数组中
}
for (int i = 0; i < m; i++)
{
cin >> tmp;//输入要查找的学生序号,注意最先进入教室的学生是stu[0];
cout << stu[tmp - 1] << endl;
}
return 0;
}

本题考察vector可变长度数组


P3613 寄包柜

题目描述

超市里有 $n(1\le n\le10^5)$ 个寄包柜。每个寄包柜格子数量不一,第 $i$ 个寄包柜有 $a_i(1\le a_i\le10^5)$ 个格子,不过我们并不知道各个 $a_i$ 的值。对于每个寄包柜,格子编号从 1 开始,一直到 $a_i$。现在有 $q(1 \le q\le10^5)$ 次操作:

  • 1 i j k:在第 $i$ 个柜子的第 $j$ 个格子存入物品 $k(0\le k\le 10^9)$。当 $k=0$ 时说明清空该格子。
  • 2 i j:查询第 $i$ 个柜子的第 $j$ 个格子中的物品是什么,保证查询的柜子有存过东西。

已知超市里共计不会超过 $10^7$ 个寄包格子,$a_i$ 是确定然而未知的,但是保证一定不小于该柜子存物品请求的格子编号的最大值。当然也有可能某些寄包柜中一个格子都没有。

输入格式

第一行 2 个整数 $n$ 和 $q$,寄包柜个数和询问次数。

接下来 $q$ 个行,每行有若干个整数,表示一次操作。

输出格式

对于查询操作时,输出答案,以换行隔开。

输入 #1

1
2
3
4
5
5 4
1 3 10000 118014
1 1 1 1
2 3 10000
2 1 1

输出 #1

1
2
118014
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<bits/stdc++.h>
using namespace std;

int n, q, i, j, k, opt;
int main()
{
cin >> n >> q;
vector<vector<int>> locker(n + 1);//初始化0到n号柜子,以便索引从1开始
while (q--)
{
cin >> opt;
if (opt == 1)//存包操作
{
cin >> i >> j >> k;
if (locker[i].size() < j + 1)//如果这个柜子不够请求的数字大
locker[i].resize(j + 1);//就扩大容量
locker[i][j] = k;
}
else {//查询操作
cin >> i >> j;
cout << locker[i][j] << endl;
}

}
return 0;
}

定义vector二维数组时要用vector<vector>,此数组二维都不定长。

结合题意,要适时扩大数组容量。


括号匹配

给定若干字符串,每个字符串由三种括号字符构成(不含空格)。如果所有的括号都可以匹配上,那么这个字符串合法,否则非法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include<bits/stdc++.h>
using namespace std;

stack<char> s;//定义一个栈
int num;
char trans(char a) {//括号的转换
if (a == ')') return '(';
if (a == '}') return '{';
if (a == '>') return '<';
return '\0'; //空字符
}
int main()
{
cin >> num;//读取测试用例数量
while (num--) {
while (!s.empty()) s.pop();//在每次处理新测试用例前,清空栈 s。
string p;//括号字符串
cin >> p;
for (int i = 0; i < p.size(); i++)//依次把字符串中的每个字符放入栈中并检验
{
if (s.empty())//如果栈为空,直接放入栈中
{
s.push(p[i]);
continue;
}
if (trans(p[i]) == s.top()) s.pop();//如果匹配就弹出
else s.push(p[i]);//否则压入栈中
}
if (s.empty()) cout << "Yes"<<endl;
else cout << "No"<<endl;
}
return 0;
}

本题使用STL的栈。


P1449 后缀表达式

题目描述

所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。

本题中运算符仅包含 $\texttt{±*/}$。保证对于 $\texttt{/}$ 运算除数不为 0。特别地,其中 $\texttt{/}$ 运算的结果需要向 0 取整(即与 C++ / 运算的规则一致)。

如:$\texttt{3*(5-2)+7}$ 对应的后缀表达式为:$\texttt{3.5.2.-*7.+@}$。在该式中,@ 为表达式的结束符号。. 为操作数的结束符号。

输入格式

输入一行一个字符串 $s$,表示后缀表达式。

输出格式

输出一个整数,表示表达式的值。

输入 #1

1
3.5.2.-*7.+@

输出 #1

1
16

说明/提示

数据保证,$1 \leq |s| \leq 50$,答案和计算过程中的每一个值的绝对值不超过 $10^9$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<bits/stdc++.h>
using namespace std;

stack<int> n;//定义一个栈
int s = 0, x, y;
int main()
{
char ch;
do{
cin >> ch;
if (ch >= '0' && ch <= '9')
s = s * 10 + ch - '0';//处理输入数字多于一位的情况
else if (ch == '.')
n.push(s), s = 0;
else if (ch != '@') {//输入的字符为运算符
x = n.top(); n.pop();//存入运算符前面的两个数,依次出栈
y = n.top(); n.pop();

switch (ch) {
case '+': n.push(x + y); break;
case '-': n.push(y - x); break;//注意是y-x
case '*': n.push(x * y); break;
case '/': n.push(y / x); break;//注意是y/x
}
}
} while (ch != '@');
cout << n.top();
return 0;
}

本题使用栈,要注意变量s的用法。

  • 如果 ch 是数字字符(09),将其转换为整数并累加到 s 中。
  • 例如,输入 123s 会依次变为 112123

P1996 约瑟夫问题

题目描述

$n$ 个人围成一圈,从第一个人开始报数,数到 $m$ 的人出列,再由下一个人重新从 $1$ 开始报数,数到 $m$ 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

注意:本题和《深入浅出-基础篇》上例题的表述稍有不同。书上表述是给出淘汰 $n-1$ 名小朋友,而该题是全部出圈。

输入格式

输入两个整数 $n,m$。

输出格式

输出一行 $n$ 个整数,按顺序输出每个出圈人的编号。

输入 #1

1
10 3

输出 #1

1
3 6 9 2 7 1 8 5 10 4

说明/提示

$1 \le m, n \le 100$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<bits/stdc++.h>
using namespace std;

queue<int> q;//定义队列
int n, m;

int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) q.push(i);//n个小朋友依次放入队列
while (q.size() != 0)
{
for (int i = 1; i < m; i++)
{
q.push(q.front());//将队首元素复制,加入到队尾
q.pop();//队首元素出队
}
cout << q.front()<<" ";//数到m的小朋友出队,不再入队
q.pop();
}
return 0;
}

本题使用队列queue


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<bits/stdc++.h>
using namespace std;

list<int> a;//定义链表
int n, m, cnt = 0;

int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) a.push_back(i);//将n个小朋友放入链表
list<int> ::iterator it, now;//两个指针
it = a.begin();//it指向表首元素

while (!a.empty())
{
cnt++;
now = it;//备份待删除元素的指针
if (++it == a.end()) it = a.begin();//it此时已经指向表尾元素,下一个再指向表首元素
if (cnt == m)//数到m时
{
cout << *now << " ";//解引用,获取当前元素的值
a.erase(now);//删除now所指元素
cnt = 0;//计数器重置
}
}
return 0;
}

本题使用链表list

注意:a.begin()为表首元素,a.end()为表尾元素的下一个位置。

使用a.erase(it)可删除it所指向的元素,但之后it变为无效状态,需要另一个迭代器备份it所指向的元素。


【数据结构1-2】二叉树

P4715 淘汰赛

题目描述

有 $2^n$($n\le7$)个国家参加世界杯决赛圈且进入淘汰赛环节。已经知道各个国家的能力值,且都不相等。能力值高的国家和能力值低的国家踢比赛时高者获胜。1 号国家和 2 号国家踢一场比赛,胜者晋级。3 号国家和 4 号国家也踢一场,胜者晋级……晋级后的国家用相同的方法继续完成赛程,直到决出冠军。给出各个国家的能力值,请问亚军是哪个国家?

输入格式

第一行一个整数 $n$,表示一共 $2^n$ 个国家参赛。

第二行 $2^n$ 个整数,第 $i$ 个整数表示编号为 $i$ 的国家的能力值($1\leq i \leq 2^n$)。

数据保证不存在平局。

输出格式

仅一个整数,表示亚军国家的编号。

输入 #1

1
2
3
4 2 3 1 10 5 9 7

输出 #1

1
1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<bits/stdc++.h>
using namespace std;

int n;
int value[260], winner[260];
//假设n=3,共8个国家参赛
void dfs(int x) {
if (x >= 1 << n) return;//当编号来到8时,说明到了最下面一层的叶子结点
else {
dfs(2 * x);//遍历左子树
dfs(2 * x + 1);//遍历右子树
int lvalue = value[2 * x], rvalue = value[2 * x + 1];//左值与右值
if (lvalue > rvalue)//左结点获胜
{
value[x] = lvalue;
winner[x] = winner[2 * x];
}
else
{
value[x] = rvalue;
winner[x] = winner[2 * x + 1];
}
}
}
int main()
{
cin >> n;
for (int i = 0; i < 1 << n; i++)
{
cin >> value[i + (1 << n)];//读入各个国家的能力值,最下面一层
winner[i + (1 << n)] = i + 1;//叶子结点的获胜方就是自己国家的编号,比如第一个国家的编号是8
}
dfs(1);//从根结点开始遍历
cout << (value[2] > value[3] ? winner[3] : winner[2]);//找到亚军

return 0;
}

本题使用二叉树结构。

注意:

从最底下一层开始逐层往上淘汰,第一个国家的编号是8。

1<<n是移位运算符,二进制的1向左移了n位,代表2^n。

本题还使用了dfs深度优先搜索。


P4913 二叉树深度

题目描述

有一个 $n(n \le 10^6)$ 个结点的二叉树。给出每个结点的两个子结点编号(均不超过 $n$),建立一棵二叉树(根节点的编号为 $1$),如果是叶子结点,则输入 0 0

建好这棵二叉树之后,请求出它的深度。二叉树的深度是指从根节点到叶子结点时,最多经过了几层。

输入格式

第一行一个整数 $n$,表示结点数。

之后 $n$ 行,第 $i$ 行两个整数 $l$、$r$,分别表示结点 $i$ 的左右子结点编号。若 $l=0$ 则表示无左子结点,$r=0$ 同理。

输出格式

一个整数,表示最大结点深度。

输入 #1

1
2
3
4
5
6
7
8
7
2 7
3 6
4 5
0 0
0 0
0 0
0 0

输出 #1

1
4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<bits/stdc++.h>
using namespace std;

const int MAXN = 2e6 + 7;
int n;
struct node {
int left, right;
}t[MAXN];
int dfs(int x) {
if (!x) return 0;//如果x不存在,也就是编号为0
return max(dfs(t[x].left), dfs(t[x].right)) + 1;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> t[i].left >> t[i].right;//读入每个结点的左右子树编号,构建二叉树
cout << dfs(1);

return 0;
}

本题使用二叉树结构和dfs,注意要使用left和right变量存储左右子树的编号。


【数据结构1-3】集合

P1551 亲戚

题目背景

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

题目描述

规定:$x$ 和 $y$ 是亲戚,$y$ 和 $z$ 是亲戚,那么 $x$ 和 $z$ 也是亲戚。如果 $x$,$y$ 是亲戚,那么 $x$ 的亲戚都是 $y$ 的亲戚,$y$ 的亲戚也都是 $x$ 的亲戚。

输入格式

第一行:三个整数 $n,m,p$,($n,m,p \le 5000$),分别表示有 $n$ 个人,$m$ 个亲戚关系,询问 $p$ 对亲戚关系。

以下 $m$ 行:每行两个数 $M_i$,$M_j$,$1 \le M_i,~M_j\le n$,表示 $M_i$ 和 $M_j$ 具有亲戚关系。

接下来 $p$ 行:每行两个数 $P_i,P_j$,询问 $P_i$ 和 $P_j$ 是否具有亲戚关系。

输出格式

$p$ 行,每行一个 YesNo。表示第 $i$ 个询问的答案为“具有”或“不具有”亲戚关系。

输入 #1

1
2
3
4
5
6
7
8
9
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

输出 #1

1
2
3
Yes
Yes
No
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<bits/stdc++.h>
using namespace std;
#define MAXN 5010

int n, m, p, x, y;
int fa[MAXN];//并查集的父节点数组,fa[i] 表示第 i 个人的父节点。
int find(int x)//查询是否是同一个集合
{
if (x == fa[x]) return x;
return find(fa[x]);//这里挺关键的,一直指向根节点
}
void join(int a, int b)//将a和b加入集合,并把a的代表换为b
{
int f1 = find(a), f2 = find(b);//寻找a和b的代表
if (f1 != f2) fa[f1] = f2;//如果代表不相同,说明a和b不在同一个集合中
}
int main()
{
cin >> n >> m >> p;
for (int i = 1; i <= n; i++) fa[i] = i;//初始化:将每个人的父节点设为自己。
for (int i = 0; i < m; i++)
{
cin >> x >> y;
join(x, y);
}
for (int i = 0; i < p; i++)
{
cin >> x >> y;
if (find(x) == find(y))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
return 0;
}

本题使用并查集。

注意:find函数

  • 查找 x 所属集合的 根节点(即集合的代表)。
  • 在查找过程中,进行 路径压缩,将路径上的所有节点直接指向根节点,优化后续查询效率。

【数据结构1-4】图的基本应用

P5318 查找文献

题目描述

小 K 喜欢翻看洛谷博客获取知识。每篇文章可能会有若干个(也有可能没有)参考文献的链接指向别的博客文章。小 K 求知欲旺盛,如果他看了某篇文章,那么他一定会去看这篇文章的参考文献(如果他之前已经看过这篇参考文献的话就不用再看它了)。

假设洛谷博客里面一共有 $n(n\le10^5)$ 篇文章(编号为 1 到 $n$)以及 $m(m\le10^6)$ 条参考文献引用关系。目前小 K 已经打开了编号为 1 的一篇文章,请帮助小 K 设计一种方法,使小 K 可以不重复、不遗漏的看完所有他能看到的文章。

这边是已经整理好的参考文献关系图,其中,文献 X → Y 表示文章 X 有参考文献 Y。不保证编号为 1 的文章没有被其他文章引用。

请对这个图分别进行 DFS 和 BFS,并输出遍历结果。如果有很多篇文章可以参阅,请先看编号较小的那篇(因此你可能需要先排序)。

输入格式

共 $m+1$ 行,第 1 行为 2 个数,$n$ 和 $m$,分别表示一共有 $n(n\le10^5)$ 篇文章(编号为 1 到 $n$)以及$m(m\le10^6)$ 条参考文献引用关系。

接下来 $m$ 行,每行有两个整数 $X,Y$ 表示文章 X 有参考文献 Y。

输出格式

共 2 行。
第一行为 DFS 遍历结果,第二行为 BFS 遍历结果。

输入 #1

1
2
3
4
5
6
7
8
9
10
8 9
1 2
1 3
1 4
2 5
2 6
3 7
4 7
4 8
7 8

输出 #1

1
2
1 2 5 6 3 7 8 4 
1 2 3 4 5 6 7 8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100050
int n, m;
vector<int> p[MAXN];
bool u[MAXN];//记录文献是否已被阅读,u[x] 为 true 表示文献 x 已被阅读。
queue<int> q;

void dfs(int x)//深度优先搜索(DFS)遍历文献
{
cout << x << " ";//此时输出的就是小K看文献的顺序
for(int i=0,sz=p[x].size();i<sz;i++)
if (!u[p[x][i]])
{
u[p[x][i]] = true;
dfs(p[x][i]);
}
}

int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
p[x].push_back(y);//用邻接表记录下文章x有参考文献y
}
// 对邻接表排序,确保优先访问编号较小的文献
for (int i = 1; i <= n; i++) {
sort(p[i].begin(), p[i].end());
}
//DFS
u[1] = true;
dfs(1);
cout << endl;

memset(u, 0, sizeof(u));
u[1] = true;
q.push(1);
//广度优先搜索(BFS)遍历文献
while (!q.empty())//如果队列不为空
{
int x = q.front();//队首赋值给x
q.pop();//始终都是队首出队
cout << x << " ";
for(int i=0,sz=p[x].size();i<sz;i++)
if (!u[p[x][i]])
{
u[p[x][i]] = true;
q.push(p[x][i]);
}
}
return 0;
}

本题考察邻接表,分别使用了DFS和BFS,DFS使用递归,BFS使用队列。

注意:题干要求如果有很多篇文章可以参阅,请先看编号较小的那篇,所以事先要对邻接表进行排序。


P3916 图的遍历

题目描述

给出 $N$ 个点,$M$ 条边的有向图,对于每个点 $v$,求 $A(v)$ 表示从点 $v$ 出发,能到达的编号最大的点。

输入格式

第 $1$ 行 $2$ 个整数 $N,M$,表示点数和边数。

接下来 $M$ 行,每行 $2$ 个整数 $U_i,V_i$,表示边 $(U_i,V_i)$。点用 $1,2,\dots,N$ 编号。

输出格式

一行 $N$ 个整数 $A(1),A(2),\dots,A(N)$。

输入 #1

1
2
3
4
4 3
1 2
2 4
4 3

输出 #1

1
4 4 3 4

说明/提示

  • 对于 $60%$ 的数据,$1 \leq N,M \leq 10^3$。
  • 对于 $100%$ 的数据,$1 \leq N,M \leq 10^5$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<bits/stdc++.h>
using namespace std;
#define MAXN 100050
int n, m;
vector<int> p[MAXN];//存放邻接表
int a[MAXN];//存放每个顶点的A值

void dfs(int x, int y)//深搜
{
if (a[x]) return;//如果该点被访问过,返回,不再进行深搜
a[x] = y;//将点x的A值更新为y,最开始是自己
for (int i = 0; i < p[x].size(); i++)
{
dfs(p[x][i], y);
}
}

int main()
{
cin >> n >> m;//输入n个点,m个边关系
for (int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
p[v].push_back(u);//建反向边
}
for (int i = n; i >= 1; i--)// 从大到小遍历每个点
{
dfs(i, i);
}
for (int i = 1; i <= n; i++)//输出每个顶点的A值
cout << a[i] << " ";
return 0;
}

本题为了降低时间复杂度,选择反向建边,从最大值点开始dfs。

  • 从编号最大的点开始,依次遍历每个点。
  • 对于每个点 u,如果它没有被访问过,则进行一次 DFS。
  • 每次 DFS 的时间复杂度是 O(N+M)。

【算法2-2】常见优化技巧

P1102 A-B 数对(双指针)

题目描述

给出一串正整数数列以及一个正整数 $C$,要求计算出所有满足 $A - B = C$ 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 $N,C$。

第二行,$N$ 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 $A - B = C$ 的数对的个数。

输入 #1

1
2
4 1
1 1 2 3

输出 #1

1
3

说明/提示

对于 $75%$ 的数据,$1 \leq N \leq 2000$。

对于 $100%$ 的数据,$1 \leq N \leq 2 \times 10^5$,$0 \leq a_i <2^{30}$,$1 \leq C < 2^{30}$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<bits/stdc++.h>
using namespace std;
#define MAXN 200010
int s[MAXN];

int main()
{
int n, c;
cin >> n >> c;
for (int i = 0; i < n; i++)
cin >> s[i];
sort(s, s + n);

int l = 0, r = 0;
long long sum = 0;
for (int i = 0; i < n; i++)
{
while (s[l] < s[i] - c && l < n)
l++;
while (s[r] <= s[i] - c && r < n)
r++;
if (s[i] - s[l] == c)
{
sum += r - l;
}
}
cout << sum;
return 0;
}

该解使用双指针,事先对数列进行排序,确保其单调递增。

对于数列中的一个数s[i],想要快速找到s[i]-C。如果这个数存在的话,一定在排序后的数列中是连续的。

维护两个指针,左指针l和右指针r,使得s[l]是首个大于等于s[i]-C的数,s[r]是首个大于s[i]-C的数。这样,从s[l]到s[r-1]都是等于s[i]-C的数字,则这样的数字共有r-l个,累加进答案中。s[i]-C是越来越大的,所以l和r都会越来越往右。

本质上是对暴力枚举的一种优化。


P1115 最大子段和(动态规划)

题目描述

给出一个长度为 $n$ 的序列 $a$,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个整数,表示序列的长度 $n$。

第二行有 $n$ 个整数,第 $i$ 个整数表示序列的第 $i$ 个数字 $a_i$。

输出格式

输出一行一个整数表示答案。

输入 #1

1
2
7
2 -4 3 -1 2 -4 3

输出 #1

1
4

说明/提示

样例 1 解释

选取 $[3, 5]$ 子段 ${3, -1, 2}$,其和为 $4$。

数据规模与约定

  • 对于 $40%$ 的数据,保证 $n \leq 2 \times 10^3$。
  • 对于 $100%$ 的数据,保证 $1 \leq n \leq 2 \times 10^5$,$-10^4 \leq a_i \leq 10^4$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n, a, f = 0, ans = -10000;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a;
f = max(f + a, a);//核心,动态规划
ans = max(f, ans);
}
cout << ans;
return 0;
}

核心逻辑

  • 对于每个元素 a,更新 fmax(f + a, a)

    • 如果 f + a 更大,说明当前元素可以加入之前的子数组。
    • 否则,从当前元素重新开始一个新的子数组。
  • 更新 ansmax(f, ans),记录全局的最大子数组和。


P7072 直播获奖(用空间换时间)

题目描述

NOI2130 即将举行。为了增加观赏性,CCF 决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为 $w%$,即当前排名前 $w%$ 的选手的最低成绩就是即时的分数线。

更具体地,若当前已评出了 $p$ 个选手的成绩,则当前计划获奖人数为 $\max(1, \lfloor p \times w %\rfloor)$,其中 $w$ 是获奖百分比,$\lfloor x \rfloor$ 表示对 $x$ 向下取整,$\max(x,y)$ 表示 $x$ 和 $y$ 中较大的数。如有选手成绩相同,则所有成绩并列的选手都能获奖,因此实际获奖人数可能比计划中多。

作为评测组的技术人员,请你帮 CCF 写一个直播程序。

输入格式

第一行有两个整数 $n, w$。分别代表选手总数与获奖率。
第二行有 $n$ 个整数,依次代表逐一评出的选手成绩。

输出格式

只有一行,包含 $n$ 个非负整数,依次代表选手成绩逐一评出后,即时的获奖分数线。相邻两个整数间用一个空格分隔。

输入 #1

1
2
10 60
200 300 400 500 600 600 0 300 200 100

输出 #1

1
200 300 400 400 400 500 400 400 300 300

说明/提示

样例 1 解释


数据规模与约定

各测试点的 $n$ 如下表:

测试点编号 $n=$
$1 \sim 3$ $10$
$4 \sim 6$ $500$
$7 \sim 10$ $2000$
$11 \sim 17$ $10^4$
$18 \sim 20$ $10^5$

对于所有测试点,每个选手的成绩均为不超过 $600$ 的非负整数,获奖百分比 $w$ 是一个正整数且 $1 \le w \le 99$。


提示

在计算计划获奖人数时,如用浮点类型的变量(如 C/C++ 中的 floatdouble,Pascal 中的 realdoubleextended 等)存储获奖比例 $w%$,则计算 $5 \times 60%$ 时的结果可能为 $3.000001$,也可能为 $2.999999$,向下取整后的结果不确定。因此,建议仅使用整型变量,以计算出准确值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
using namespace std;
int t[610], n, w;
int main()
{
int tmp;
cin >> n >> w;
for (int i = 1; i <= n; i++)
{
cin >> tmp;
t[tmp]++;//加入对应分数的桶中
int sum = 0;//累加人数
for (int j = 600; j >= 0; j--)
{
sum += t[j];
if (sum >= max(1, i * w / 100))//满足获奖人数时
{
cout << j << " ";//输出获奖的最后一名的成绩
break;
}
}
}
return 0;
}

发现需要排序的数值不超过600,因此可以考虑计数排序。

每次评出一个人的成绩就加入对应的桶中,然后从高到低统计累加人数,达到获奖人数时输出当时的分数线。

注意:i * w / 100中使用了除号,所以这个算式是下取整的。


经典算法

01背包

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
using namespace std;
const int N = 1010;
int f[N][N]; // f[i][j]: 前i个物品,容量j时的最大价值
int w[N], v[N]; // w存重量,v存价值
int n, m;

int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {//输入每个物品的重量和价值
cin >> w[i] >> v[i];
}

// 初始化:没有物品时价值为0
memset(f, 0, sizeof(f));

for (int i = 1; i <= n; ++i) { // 枚举物品
for (int j = 0; j <= m; ++j) // 枚举容量
{
if (j >= w[i]) { // 能装下第i个物品的重量
f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]); //比较不选和选的状态,取更大的
}
else f[i][j] = f[i - 1][j]; // 不选第i个物品
}
}
cout << f[n][m] << endl;
return 0;
}

该解使用二维DP,更好理解。动态规划的核心就是“活在当下”,只考虑当前的状态,过去的状态已经记录好了。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
using namespace std;
const int N = 1010;
int f[N]; // f[j]: 容量j时的最大价值
int w[N], v[N]; // w存重量,v存价值
int n, m;

int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {//输入每个物品的重量和价值
cin >> w[i] >> v[i];
}

// 初始化:没有物品时价值为0
memset(f, 0, sizeof(f));

for (int i = 1; i <= n; ++i) { // 枚举物品
for (int j = m; j >= 0; j--)
{
if (j >= w[i])
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
cout << f[m] << endl;
return 0;
}

该解使用一维DP。

观察二维数组的转移方程可以发现,计算 dp[i][j] 仅依赖 dp[i-1][j]dp[i-1][j-w[i]]

因此,可以用一维数组 dp[j] 保存前一行的状态,并在遍历时覆盖它。

通过从大到小遍历容量,确保在计算 dp[j] 时,dp[j - w[i]] 仍然是上一轮(未更新)的结果。

动态规划核心思想

  1. 状态压缩:用一维数组替代二维数组,节省空间。N=1e5时,二维数组的数据就会溢出。

  2. 逆序遍历:确保每个物品只被选中一次(防止重复计算)。

  3. 贪心选择:每一步都选择当前最优解(是否放入当前物品)。


一维差分

输入样例

1
2
3
4
5
5 1 2
1 1 1 2 2
1 4 2
1 3
1 5

输出样例

1
2
9
15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 +10;
int a[N],d[N];
int n, p, q;
int l, r, x;
int sum = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> p >> q;
for (int i = 1; i <= n; i++)//下标从1开始
{
cin >> a[i];
}
d[1] = a[1];
for (int i = 2; i <= n; i++)//构建一维差分数组
{
d[i] = a[i] - a[i - 1];
}

for (int i = 0; i < p; i++)//区间加
{
cin >> l >> r >> x;
d[l] += x;
d[r + 1] -= x;
}
for (int i = 1; i <= n; i++)//恢复a数组
{
a[i] = a[i - 1] + d[i];
}
for (int i = 0; i < q; i++)
{
cin >> l >> r;
for (int i = l; i <= r; i++)
{
sum += a[i];
}
cout << sum << endl;
sum = 0;//重置
}
return 0;
}

一维前缀和

输入样例

1
2
3
4
5
6
7
8
9
10
2
5 3
1 2 3 4 5
1 2
2 5
3 4
7 2
-1 9 -10 8 2 6 11
1 5
2 7

输出样例

1
2
3
4
5
3
14
7
8
26
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 + 10;
int T;
int n, q;
int l, r;
int a[N], s[N];
int sum = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> T;
while (T--)
{
cin >> n >> q;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
s[1] = a[1];
for (int i = 2; i <= n; i++)//构建前缀和数组
{
s[i] = s[i - 1] + a[i];
}
while (q--)
{
cin >> l >> r;
sum = s[r] - s[l - 1];
cout << sum << endl;
sum = 0;
}
}
return 0;
}

DFS深度优先搜索

可以参考这篇博客:

https://blog.csdn.net/m0_46549425/article/details/108025133?fromshare=blogdetail&sharetype=blogdetail&sharerId=108025133&sharerefer=PC&sharesource=Xing1796_&sharefrom=from_link


ACM蓝桥杯竞赛入门

顺序结构程序设计

题目 1761: 学习ASCII码

时间限制: 2s 内存限制: 192MB 提交: 5115 解决: 2910

题目描述

刚开始学C语言,ASCII码可是必须要会的哦!那么问题来了,要求你用熟悉的printf输出字符常量 ’ t ’ 的ASCII以及ASCII码值63对应的字符!

注意,是两个结果,一个数字,一个字符,用空格隔开!

输入格式

输出格式

字符常量 ’ t ’ 的ASCII以及ASCII码值63对应的字符!

1
2
3
4
5
6
7
8
9
#include <bits/stdc++.h>//万能头文件
using namespace std;


int main()
{
cout << int('t') << " " << char(63);//是这样的格式
return 0;
}

题目 1762: printf基础练习

题目描述

继续练习printf函数,要求你输出123456789这个数字的八进制与十六进制,不要忘记他们的前缀哦!

输入格式

输出格式

123456789这个数字的八进制和十六进制数

两个结果占一行,空格分开

1
2
3
4
5
6
7
8
9
10
#include <bits/stdc++.h>//万能头文件
using namespace std;

int main()
{
int num = 123456789;
cout << oct << showbase << num << " " << hex << showbase << num;

return 0;
}

解题思路:

使用cout结合操纵符oct(八进制输出)和showbase(显示进制前缀)输出八进制形式的数字,

接着使用hex(十六进制输出)和showbase输出十六进制形式的数字,中间用空格分开。


题目 1056: 温度转换

题目描述

输入一个华氏温度,要求输出摄氏温度。公式为

二级C语言-温度转换

保留两位小数

样例输入

1
-40

样例输出

1
-40.00
1
2
3
4
5
6
7
8
9
10
11
12
#include <bits/stdc++.h>//万能头文件
using namespace std;

int main()
{
double a, b;
cin >> a;
b = (double)5 / 9 * (a - 32);
cout << setprecision(2) << fixed << b;

return 0;
}

表达式 5 / 9整数除法,即使 abdouble 类型,但 59整型字面量,默认为 int 类型

编译器会优先执行整数除法(结果为 0),再与 (a-32) 相乘,最终结果必然为 0


题目 2757: 浮点数向零舍入

题目描述

输入一个单精度浮点数,将其向零舍入到整数。

说明:向零舍入的含义是,正数向下舍入,负数向上舍入。

提示:可以使用强制类型转换来实现。

输入格式

一个单精度浮点数。

输出格式

一个整数,即向零舍入到整数的结果。

样例输入

1
2.3

样例输出

1
2
1
2
3
4
5
6
7
8
9
10
11
#include <bits/stdc++.h>//万能头文件
using namespace std;

int main()
{
float a;
cin >> a;
cout << (int)a;//强转为整型即可

return 0;
}

不需要用分支,ceil和floor函数。


循环结构程序设计

题目 2809: 菲波那契数列

题目描述

菲波那契数列是指这样的数列: 数列的第一个和第二个数都为1,接下来每个数都等于前面2个数之和。
给出一个正整数k,要求菲波那契数列中第k个数是多少。

输入格式

输入一行,包含一个正整数k。(1 <= k <= 46)

输出格式

输出一行,包含一个正整数,表示菲波那契数列中第k个数的大小

样例输入

1
19

样例输出

1
4181
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h>//万能头文件
using namespace std;


int main()
{
int k;
cin >> k;
int ans = 0;
if (k == 1 || k == 2)
{
cout << 1;
return 0;
}
int a = 1, b = 1;
for (int i = 3; i <= k; i++)
{
ans = a + b;
a = b;
b = ans;
}
cout << ans;

return 0;
}

该解不使用数组,需要另外维护两个变量a,b,使它们一直存储前两个数。


题目 3014: 计算星期几

题目描述

假设今天是星期日,那么过ab天之后是星期几?

输入格式

两个正整数a,b,中间用单个空格隔开。0<a≤100, 0<b≤10000。

输出格式

一个字符串,代表过ab天之后是星期几。
其中,Monday是星期一,Tuesday是星期二,Wednesday是星期三,Thursday是星期四,Friday是星期五,Saturday是星期六,Sunday是星期日。

样例输入

1
3 2000

样例输出

1
Tuesday

提示

注意数据大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <bits/stdc++.h>//万能头文件
using namespace std;

int a, b;
int day = 1;
int main()
{
cin >> a >> b;
for (int i = 1; i <= b; i++)
{
day *= a;
day %= 7;//每次循环都要进行%取余运算,不然会造成数据溢出
}

switch (day)
{
case 1:
cout << "Monday";
break;
case 2:
cout << "Tuesday";
break;
case 3:
cout << "Wednesday";
break;
case 4:
cout << "Thursday";
break;
case 5:
cout << "Friday";
break;
case 6:
cout << "Saturday";
break;
case 0:
cout << "Sunday";
break;
default:
cout << "无效";
break;
}
return 0;
}

该题涉及到指数,一定要注意数据的溢出问题。

逐步取余与一次性取余的等价性:由于模运算的分配性,逐步在每次乘法后取余与最后一次性取余的结果是相同的。


题目 3015: 幂的末尾

题目描述

幂ab的末3位数是多少?

输入格式

两个正整数a,b。1≤a≤100,1≤b≤10000。

输出格式

从高位到低位输出幂的末三位数字,中间无分隔符。若幂本身不足三位,在前面补零。

样例输入

1
7 2011

样例输出

1
743
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <bits/stdc++.h>//万能头文件
using namespace std;

int a, b;
int day = 1;
int main()
{
cin >> a >> b;
for (int i = 1; i <= b; i++)
{
day *= a;
day %= 1000;//每次循环都要进行%取余运算,不然会造成数据溢出
}
cout <<setw(3)<<setfill('0') << day;

return 0;
}

与上一题一样,在循环内取余。

注意:setfill()括号里的是字符,char类型。


题目 3013: 求小数的某一位

题目描述

分数a/b化为小数后,小数点后第n位的数字是多少?

输入格式

三个正整数a,b,na,b,n,相邻两个数之间用单个空格隔开。0<a<b<100,1<=n<=100000<a<b<100,1<=n<=10000。

输出格式

一个数字。

样例输入

1
1 2 1

样例输出

1
5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <bits/stdc++.h>//万能头文件
using namespace std;

int a, b, n, x = 0;
int main()
{
cin >> a >> b >> n;
for (int i = 0; i < n; i++)
{
x = a * 10 / b;//取当前位
a = a * 10 % b;//得到剩下的余数
}
cout << x;
return 0;
}

代码很简单,思想很精妙。


题目 1011: 最大公约数与最小公倍数

题目描述

输入两个正整数m和n,求其最大公约数和最小公倍数。

输入格式

两个整数

输出格式

最大公约数,最小公倍数

样例输入

1
5 7

样例输出

1
1 35
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>//万能头文件
using namespace std;

int m, n;
int a = 0, b = 0;
int main()
{
cin >> m >> n;

int max = m;
if (n > m)
max = n;

//最大公约数
//我们从较大的那一个值开始枚举
//当可以同时整除n和m的时候,即目标所求
for (int i = max; i >= 1; i--)
{
if (m % i == 0 && n % i == 0)
{
a = i;
break;//求最大公约数,找到即最大,立刻跳出
}
}

//最小公倍数
//最小公倍数,两个数的成绩一定是这两个数的公倍数
//我们假设当前公倍数就是最小公倍数,我们从这个位置开始往前枚举,如果存在别的公倍数,那么就更新
for (int i = m * n; i >= 1; i--)
{
if (i % m == 0 && i % n == 0)
{
b = i;//求最小公倍数,一直向下找,不需要跳出
}
}

cout << a << " " << b;
return 0;
}

无需STL函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <bits/stdc++.h>//万能头文件
using namespace std;

int m, n;
int a = 0, b = 0;
int main()
{
cin >> m >> n;

a = gcd(m, n);//C++17
b = lcm(m, n);//C++20

cout << a << " " << b;
return 0;
}

STL函数,但是需要更高级的C++版本。


题目 2817: 级数求和

题目描述

已知:Sn= 1+1/2+1/3+…+1/n。显然对于任意一个整数K,当n足够大的时候,Sn大于K。

现给出一个整数K(1<=k<=15),要求计算出一个最小的n;使得Sn>K。

输入格式

一个整数K。

输出格式

一个整数n。

样例输入

1
1

样例输出

1
2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <bits/stdc++.h>//万能头文件
using namespace std;

int main()
{

int k;
int n = 0;//这里不可以是double类型
double sum = 0;

cin >> k;
while (sum <= k)
{
n++;
sum += 1.0 / n;
}
cout << n;
return 0;
}

涉及数的累加,尽量使用int类型变量。

浮点数累加可能会产生精度的影响,比如本该是1,2,3,4,却变成了1,2,3,3.99999。


题目 1464: 分解质因数

题目描述

求出区间[a,b]中所有整数的质因数分解。

提示

先筛出所有素数,然后再分解。
数据规模和约定

输入格式

输入两个整数a,b。

2< =a< =b< =10000

输出格式

每行输出一个数的分解,形如k=a1a2a3…(a1< =a2< =a3…,k也是从小到大的)(具体可看样例)

样例输入

1
3 10

样例输出

1
2
3
4
5
6
7
8
3=3
4=2*2
5=5
6=2*3
7=7
8=2*2*2
9=3*3
10=2*5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <bits/stdc++.h>//万能头文件
using namespace std;

vector<int> p;
int n = 0, t = 0;
void prim_10000()//求出10000以内所有的质数
{
int flag = 0;
for (int i = 2; i <= 10000; i++)
{
flag = 0;//重置
for (int j = 2; j <= sqrt(i); j++)
{
if (i % j == 0)
{
flag = 1;
break;
}
}
if (flag == 0)
p.push_back(i);
}
}

int main()
{
int a, b;
cin >> a >> b;

prim_10000();
for (int i = a; i <= b; i++)
{
t = i;
cout << t << "=";
for (int j = 0; j < p.size(); j++)//用存好的质数轮流检查
{
if (t % p[j] == 0)
{
t /= p[j];
if (t != 1)
cout << p[j] << "*";
else
{
cout << p[j] << endl;//分解完了
break;
}
j--;//继续检查刚刚的p[j]
}
}
}
return 0;
}

要注意的细节挺多的。


题目 2825: 计算多项式的值

题目描述

假定多项式的形式为xn+xn-1+…+x2+x+1,请计算给定单精度浮点数x和正整数n值的情况下这个多项式的值。

输入格式

输入仅一行,包括x和n,用单个空格隔开。x在float范围内,n <= 1000000。

输出格式

输出一个实数,即多项式的值,精确到小数点后两位。保证最终结果在float范围内。

样例输入

1
2.0 4

样例输出

1
31.00
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <bits/stdc++.h>//万能头文件
using namespace std;

double x;
int n;
double sum = 0;
double func(double a, int n)//计算整数幂
{
double sum = 1.0;
for (int i = 0; i < n; i++)
{
sum *= a;
}
return sum;
}
int main()
{
cin >> x >> n;
double t = 0;
for (int i = 0; i <= n; i++)
{
t = func(x, i);
sum += t;
}
cout << setprecision(2) << fixed << sum;
return 0;
}

注意

计算整数幂最好使用double类型,位数更多,不容易溢出。

对于整数幂的计算,使用循环乘法(即逐次相乘)比使用 pow 函数更高效且精度更高。


题目 1231: 杨辉三角

题目描述

还记得中学时候学过的杨辉三角吗?具体的定义这里不再描述,你可以参考以下的图形:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1

输入格式

输入数据包含多个测试实例,每个测试实例的输入只包含一个正整数n(1<=n<=30),表示将要输出的杨辉三角的层数。

输出格式

对应于每一个输入,请输出相应层数的杨辉三角,每一层的整数之间用一个空格隔开,每一个杨辉三角后面加一个空行。

样例输入

1
2 3

样例输出

1
2
3
4
5
6
1
1 1

1
1 1
1 2 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <bits/stdc++.h>//万能头文件
using namespace std;

const int N = 100;
int n;
int a[N][N];

int main()
{
while (cin >> n)
{
if (n >= 1)
a[1][1] = 1;
if (n >= 2)
{
a[2][1] = 1;
a[2][2] = 1;
}
if (n >= 3)
{
for (int i = 3; i <= n; i++)
{
a[i][1] = 1;//将该行的第一个元素赋值为1
a[i][i] = 1;//将该行的最后一个元素赋值为1
for (int j = 2; j <= i - 1; j++)//处理中间的元素
{
a[i][j] = a[i - 1][j] + a[i - 1][j - 1];//该元素=其位置上一行的元素+其位置上一行的元素的左边的元素
}
}
}
for (int i = 1; i <= n; i++)//输出
{
for (int j = 1; j <= i; j++)
cout << a[i][j] << " ";
cout << endl;
}
cout << endl;
}

return 0;
}

函数

题目 1017: 完数的判断

题目描述

一个数如果恰好等于不包含它本身所有因子之和,这个数就称为"完数"。 例如,6的因子为1、2、3,而6=1+2+3,因此6是"完数"。 编程序找出N之内的所有完数,并按下面格式输出其因子

输入格式

N

输出格式

? its factors are ? ? ?

样例输入

1
1000

样例输出

1
2
3
6 its factors are 1 2 3 
28 its factors are 1 2 4 7 14
496 its factors are 1 2 4 8 16 31 62 124 248
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>//万能头文件
using namespace std;

const int N = 1e4 + 10;
int n;
map<int, vector<int>> st;
int sum = 0;
void func(int x)//分解因数
{
for (int i = 1; i <= x / 2; i++)//x/2即可
{
if (x % i == 0)
st[x].push_back(i);
}
}
int main()
{
cin >> n;
for (int i = 2; i <= n; i++)//分解n以内每个数的因数
{
func(i);
}
for (int i = 2; i <= n; i++)
{
for (int num : st[i])//对每个st[i]中的元素进行累加
{
sum += num;
}
if (i == sum)//判断
{
cout << i << " " <<"its factors are" << " ";
for (int num : st[i])
cout << num << " ";
cout << endl;
}
sum = 0;//重置
}
return 0;
}

分解因数,使用了map。


蓝桥杯真题

2024年第十五届蓝桥杯大赛软件类省赛C/C++大学A组真题

试题A: 艺术与篮球(模拟)

【问题描述】

小蓝出生在一个艺术与运动并重的家庭中。

妈妈是位书法家,她希望小蓝能通过练习书法,继承她的艺术天赋,并练就一手好字。爸爸是一名篮球教练,他希望小蓝能通过篮球锻炼身体,培养运

动的激情和团队合作的精神。

为了既满足妈妈的期望,又不辜负爸爸的心意,小蓝决定根据日期的笔画数来安排自己的练习。首先,他会将当天的日期按照“YYYYMMDD”的格式转换成一个8 位数,然后将这8 位数对应到汉字上,计算这些汉字的总笔画数。如果总笔画数超过50,他就去练习篮球;如果总笔画数不超过50,他就去练习书法。

例如,在2024 年1 月1 日这天,日期可表示为一个8 位数字20240101,其转换为汉字是“二零二四零一零一”。日期的总笔画数为2+13+2+5+13+1 + 13 + 1 =50,因此在这天,小蓝会去练习书法。

以下是汉字的笔画数对照表:

A题艺术与篮球

现在,请你帮助小蓝统计一下,在2000 年1 月1 日到2024 年4 月13 日这段时间内,小蓝有多少天是在练习篮球?

【答案提交】

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

int s[10] = { 13,1,2,3,5,4,4,2,2,2 };//存储每个汉字的笔画数
int y = 2000, m = 1, d = 1;//初始化日期为第一天
int sum = 0, ans = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

while (1) {
if (m == 1 || m == 3 || m == 5 || m == 7 || m == 8 || m == 10 || m == 12) {//大月31天
if (d > 31) {
m++;
d = 1;
}
}
else if (m == 2) {
if ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) //闰年,2月多一天是29天
{
if (d > 29) {
m++;
d = 1;
}
}
else
{
if (d > 28) {
m++;
d = 1;
}
}
}
else {//小月30天
if (d > 30) {
m++;
d = 1;
}
}

if (m > 12) {
y++;
m = 1;
}

sum = 0;
//计算每一个数字的笔画数,年、月、日
sum += s[y % 10] + s[y / 10 % 10] + s[y / 100 % 10] + s[y / 1000 % 10];
sum += s[m % 10] + s[m / 10];//2月表示为02
sum += s[d % 10] + s[d / 10];//2日表示为02
if (sum > 50)
ans++;

if (y == 2024 && m == 4 && d == 13)//停止枚举
break;

d++;
}
cout << ans;
return 0;
}

枚举每一天,依次进行判断。核心是区分好大月、小月和2月。


题目 3215: 训练士兵(模拟)

题目描述

在蓝桥王国中,有 n 名士兵,这些士兵需要接受一系列特殊的训练,以提升他们的战斗技能。对于第 i 名士兵来说,进行一次训练所需的成本为 pi 枚金币,而要想成为顶尖战士,他至少需要进行 ci 次训练。

为了确保训练的高效性,王国推出了一种组团训练的方案。该方案包含每位士兵所需的一次训练,且总共只需支付 S 枚金币(组团训练方案可以多次购买,即士兵可以进行多次组团训练)。

作为训练指挥官,请你计算出最少需要花费多少金币,才能使得所有的士兵都成为顶尖战士?

输入格式

输入的第一行包含两个整数 n 和 S ,用一个空格分隔,表示士兵的数量和进行一次组团训练所需的金币数。接下来的 n 行,每行包含两个整数 pi 和 ci ,用一个空格分隔,表示第 i 名士兵进行一次训练的金币成本和要成为顶尖战士所需的训练次数。

输出格式

输出一行包含一个整数,表示使所有士兵成为顶尖战士所需的最少金币数。

样例输入

1
2
3
4
3 6
5 2
2 4
3 2

样例输出

1
16

提示

【样例说明】

花费金币最少的训练方式为:进行 2 次组团训练,花费 2 × 6 = 12 枚金币,此时士兵 1, 3 已成为顶尖战士;再花费 4 枚金币,让士兵 2 进行两次训练,成为顶尖战士。总花费为 12 + 4 = 16。

【评测用例规模与约定】

对于 40% 的评测用例,1 ≤ n ≤ 103,1 ≤ pi, ci ≤ 105,1 ≤ S ≤ 107。

对于所有评测用例,1 ≤ n ≤ 105,1 ≤ pi, ci ≤ 106,1 ≤ S ≤ 1010。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 + 10;
int n, s;
int a[N],b[N];
int sum_a = 0, sum_b = 0, ans = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);
cin >> n >> s;
for (int i = 1; i <= n; i++)
{
cin >> a[i] >> b[i];
sum_a += a[i];//存放每个人独自升级所需要的费用
sum_b += b[i];//存放每个人距离升满级还需要的次数
}
while (sum_a != 0 && sum_b != 0)
{
if (sum_a >= s)//如果每个人独自升级的费用之和>=s,那就选择组团升级
{
ans += s;//选择组团升级
for (int i = 1; i <= n; i++)
{
b[i]--;//还需要升级的次数集体减一
if (b[i] == 0)
sum_a -= a[i];//去除已经升满级的士兵
}
}
else//独自升级
{
for (int i = 1; i <= n; i++)
{
if (b[i] > 0)
{
ans += a[i];
b[i]--;//还需要升级的次数减一
if (b[i] == 0)
sum_a -= a[i];
}
}
}
}
cout << ans;
return 0;//必须要写
}

MyAns

根据题意暴力模拟,只能拿55分,其他样例超时了。

超时原因分析:

问题分析

  1. 每次组团训练减少次数过于简单
    • 你的代码每次判断 sum_a >= s 时,就直接减少所有士兵的一次训练次数 b[i]--,但没有合理地分配组团训练的次数。
    • 这导致循环执行次数过多,时间复杂度接近 O(n * max(ci)),在最坏情况下,可能达到 10^10 级别,明显超时。
  2. 逐个士兵单独训练过于低效
    • else 语句里,你是按顺序遍历每个士兵,并逐个减少 b[i]--,这样在 n 很大时,单独训练的次数可能会很多,增加了时间复杂度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 + 10;
int n, s;
int sum_p = 0, ans = 0;
struct Soldier//结构体
{
int p, c;
}sd[N];

bool cmp(Soldier a, Soldier b)
{
return a.c < b.c;//按升满级所需要次数从小到大排序
}

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);
cin >> n >> s;
for (int i = 0; i < n; i++)
{
cin >> sd[i].p >> sd[i].c;
sum_p += sd[i].p;//存放每个士兵独自升级所需要的金币之和
}
sort(sd, sd + n, cmp);

int t = 0;//已经进行过组团训练的次数
for (int i = 0; i < n; i++)//对每个士兵依次进行判断
{
if (sum_p >= s) {//选择组团升级
ans += (sd[i].c - t) * s;
t += sd[i].c - t;//更新t
}
else {//选择独自升级
ans += sd[i].p * (sd[i].c - t);
}
sum_p -= sd[i].p;//去除该士兵
}
cout << ans;
return 0;//必须要写
}

100分答案

核心是变量t的使用。


题目 3217: 成绩统计(模拟)

题目描述

小蓝的班上有 n 个人,一次考试之后小蓝想统计同学们的成绩,第 i 名同学的成绩为 ai 。当小蓝统计完前 x 名同学的成绩后,他可以从 1 ∼ x 中选出任意 k 名同学的成绩,计算出这 k 个成绩的方差。小蓝至少要检查多少个人的成绩,才有可能选出 k 名同学,他们的方差小于一个给定的值 T ?

提示:k 个数 v1, v2, · · · , vk 的方差 σ2 定义为:σ2 =∑ki=1(vi−v’)/k ,其中 v’ 表示v 的平均值,v’ =∑ki=1 vi/k 。

输入格式

输入的第一行包含三个正整数 n, k, T ,相邻整数之间使用一个空格分隔。

第二行包含 n 个正整数 a1, a2, · · · , an ,相邻整数之间使用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。如果不能满足条件,输出 −1 。

样例输入

1
2
5 3 1
3 2 5 2 3

样例输出

1
4

提示

【样例说明】

检查完前三名同学的成绩后,只能选出 3, 2, 5 ,方差为 1.56 ;检查完前四名同学的成绩后,可以选出 3, 2, 2 ,方差为 0.33 < 1 ,所以答案为 4 。

【评测用例规模与约定】

对于 10% 的评测用例,保证 1 ≤ n, k ≤ 102;

对于 30% 的评测用例,保证 1 ≤ n, k ≤ 103 ;

对于所有评测用例,保证 1 ≤ n, k ≤ 105 ,1 ≤ T ≤ 231 − 1 ,1 ≤ ai ≤ n 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 + 10;
int n, k, T;
int a[N], b[N];
double avg = 0, v = 0;//avg表示k个成绩的均值,v表示与均值差距最大的数
int dex = 0;//存放k个数中与均值差距最大的数的下标
bool calculate() {//计算k个成绩的方差并验证
double sum = 0;//最终的方差
avg = 0;//计算均值
for (int i = 1; i <= k; i++) {
avg += b[i];
}
avg /= k;//更新avg

for (int i = 1; i <= k; i++) {
sum += (b[i] - avg) * (b[i] - avg);
}
sum /= k;
if (sum < T)
return 1;
else
return 0;
}

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> k >> T;

//这里不判断的话将少一半分数
if (n < k)//如果n小于k就不需要考虑了,直接输出-1,结束程序
{
cout << -1;
return 0;
}

for (int i = 1; i <= n; i++)//存入每个同学的成绩
{
cin >> a[i];
if (i <= k) {//先将前k个成绩存入b数组中
b[i] = a[i];
avg += b[i];
}
}
avg /= 10;

if (calculate()) {//如果前k个成绩的方差已经小于T了,直接输出下标并结束程序
cout << k;
return 0;
}

v = b[1];//令其先等于第一个数
dex = 1;
for (int i = 2; i <= k; i++) {//找出k个数中与均值差距最大的数
if (abs(b[i] - avg) > abs(v - avg)) {
v = b[i];
dex = i;//存放该数的下标
}
}

for (int i = k + 1; i <= n; i++)
{
if (abs(a[i] - avg) < abs(v - avg)) {//往后找一个与均值差距较小的
b[dex] = a[i];//替换
if (calculate()) {//再次计算方差
cout << i;
return 0;
}
else {//如果方差还是不小于T,在新的k个成绩中再次找到一个与均值差距最大的数
v = b[1];//令其先等于第一个数
dex = 1;
for (int j = 2; j <= k; j++) {//在数组b中找出k个数中与均值差距最大的数
if (abs(b[j] - avg) > abs(v - avg)) {
v = b[j];
dex = j;//存放该数的下标
}
}
}
}
}
cout << -1;//遍历完全部成绩,依然没有找到
return 0;//必须要写
}

根据题意模拟,思路挺清晰的。


题目 3216: 团建(DFS+哈希表)

题目描述

小蓝正在和朋友们团建,有一个游戏项目需要两人合作,两个人分别拿到一棵大小为 n 和 m 的树,树上的每个结点上有一个正整数权值。

两个人需要从各自树的根结点 1 出发走向某个叶结点,从根到这个叶结点的路径上经过的所有结点上的权值构成了一个正整数序列,两人的序列的最长公共前缀即为他们的得分。给出两棵树,请计算两个人最多的得分是多少。

输入格式

输入的第一行包含两个正整数 n, m ,用一个空格分隔。

第二行包含 n 个正整数 c1, c2, · · · , cn ,相邻整数之间使用一个空格分隔,其中 ci 表示第一棵树结点 i 上的权值。

第三行包含 m 个正整数 d1, d2, · · · , dm ,相邻整数之间使用一个空格分隔,其中 di 表示第二棵树结点 i 上的权值。

接下来 n − 1 行,每行包含两个正整数 ui, vi 表示第一棵树中包含一条 ui 和vi 之间的边。

接下来 m − 1 行,每行包含两个正整数 pi, qi 表示第二棵树中包含一条 pi和 qi 之间的边。

输出格式

输出一行包含一个整数表示答案。

样例输入

1
2
3
4
5
2 2
10 20
10 30
1 2
2 1

样例输出

1
1

提示

【样例说明】两个序列可以为 [10, 20] , [10, 30] ,最大前缀为 1 ;

【样例输入】

5 4

10 20 30 40 50

10 40 20 30

1 2

1 3

2 4

3 5

1 2

1 3

3 4

【样例输出】

2

【样例说明】

两个序列可以为 [10, 20, 40] , [10, 20, 30] ,最大前缀为 2 。

【评测用例规模与约定】

对于 20% 的评测用例,1 ≤ n, m ≤ 500 ;对于所有评测用例,1 ≤ n, m ≤ 2 × 105,1 ≤ ci, di ≤ 108 ,1 ≤ ui, vi ≤ n ,1 ≤ pi, qi ≤ m ,对于任意结点,其儿子结点的权重互不相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 2e5 + 10 ;
int n, m;
int a[N], b[N];
vector<int> c[N], d[N];
int ans = 0;

/*参数含义:
len:当前公共前缀长度。

x:当前在树 A 的节点编号。

y:当前在树 B 的节点编号。

fa_x、fa_y:分别是 A 和 B 当前节点的“父节点”,用于避免回到上一个节点(防止死循环)。*/

void dfs(int len, int x, int y, int fa_x, int fa_y) {
ans = max(ans, len);
unordered_map<int,int> t;//哈希表

for (auto it : c[x]) {//建立 A树当前节点 的孩子节点权值到节点编号的映射
if (it != fa_x) {//这里的it只是变量,并不是迭代器
t[a[it]] = it;
}
}
for (auto it : d[y]) {//遍历 B树当前节点 的孩子节点
if (it != fa_y) {
if (t.count(b[it])) {//如果B树的孩子节点的权值可以在A树中找到,那么就len+1,同步往下一层找
dfs(len + 1, t[b[it]], it, x, y);
}
}
}
}

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> m;
for (int i = 1; i <= n; i++) {//存入节点的权值
cin >> a[i];//A树
}
for (int i = 1; i <= m; i++) {
cin >> b[i];//B树
}
for (int i = 1; i < n; i++) {//构建邻接表
int x, y;
cin >> x >> y;
c[x].push_back(y);//A树
c[y].push_back(x);
}
for (int i = 1; i < m; i++) {
int x, y;
cin >> x >> y;
d[x].push_back(y);//B树
d[y].push_back(x);
}
if (a[1] == b[1])
dfs(1,1,1,0,0);
else
cout << 0;
cout << ans;
return 0;//必须要写
}

挺简单的dfs,同步深搜思想。


2024年第十五届蓝桥杯大赛软件类省赛C/C++大学B组真题

题目 3209: 好数(暴力)

题目描述

一个整数如果按从低位到高位的顺序,奇数位(个位、百位、万位 · · · )上的数字是奇数,偶数位(十位、千位、十万位 · · · )上的数字是偶数,我们就称之为“好数”。给定一个正整数 N,请计算从 1 到 N 一共有多少个好数。

输入格式

一个整数 N。

输出格式

一个整数代表答案。

样例输入

1
24

样例输出

1
7

提示

对于第一个样例,24 以内的好数有 1、3、5、7、9、21、23,一共 7 个。试题 C: 好数 4第十五届蓝桥杯大赛软件赛省赛 C/C++ 大学 B 组【评测用例规模与约定】对于 10% 的评测用例,1 ≤ N ≤ 100。对于 100% 的评测用例,1 ≤ N ≤ 107。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e7 + 10;
int n;
int ans = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
for (int i = 1; i <= n; i++)
{
int t = i;
string s = to_string(i);//转换为字符串,方便统计该数的总位数
int q = 0;
int flag = 1;//用来标记现在进行到第几位
while (t != 0)
{
q = t % 10;//逐位取
t /= 10;
if (flag % 2 != 0 && q % 2 != 0)//奇数位上的数字是奇数
{
flag++;
continue;
}
else if (flag % 2 == 0 && q % 2 == 0)//偶数位上的数字是偶数
{
flag++;
continue;
}
else {
break;//若都不满足,直接跳出,考虑下一个数
}
}
if (--flag == s.size())//如果成功遍历完,那就说明这个数是好数
ans++;
flag = 0;//重置

}
cout << ans;
return 0;//必须要写
}

MyAns

直接暴力即可,对每个数逐位判断。

不会超时的理由:

虽然 O(n log n)N = 10^7 时理论上会有 7 × 10^7 次操作,

但由于整数运算高效、输入优化以及编译器优化,程序运行时间仍然在 1 秒以内,因此不会超时。


2024年第十五届蓝桥杯大赛软件类省赛C/C++大学C组真题

试题A: 拼正方形(本题总分:5 分)

【问题描述】

小蓝正在玩拼图游戏,他有7385137888721 个2 x 2 的方块10470245 个1 x 1 的方块,他需要从中挑出一些来拼出一个正方形,比如用3 个2 x 2 和4个1 x 1 的方块可以拼出一个4 x 4 的正方形,用9 个2 x 2 的方块可以拼出一个6 x 6 的正方形,请问小蓝能拼成的最大的正方形的边长为多少。

【答案提交】

这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


可以利用数学的思想,先全部使用22的方块。每个方块的面积是4。
$$
L^2=4a
$$
开平方根得:
$$
L=\sqrt{4a}
$$
使用计算器计算并下取整得:
$$
L=5435122
$$
接着尝试使用1
1的方块在外面围一圈:
$$
4\times5435122+4=21740492>10470245
$$
远远大于1*1的方块数量,故答案就是5435122


题目 3221: 数字诗意(数学规律)

题目描述

在诗人的眼中,数字是生活的韵律,也是诗意的表达。

小蓝,当代顶级诗人与数学家,被赋予了 “数学诗人” 的美誉。他擅长将冰冷的数字与抽象的诗意相融合,并用优雅的文字将数学之美展现于纸上。

某日,小蓝静坐书桌前,目光所及,展现着 n 个数字,它们依次为a1, a2, . . . , an,熠熠生辉。小蓝悟到,如果一个数能够以若干个(至少两个)连续的正整数相加表示,那么它就蕴含诗意。例如,数字 6 就蕴含诗意,因为它可以表示为 1 + 2 + 3。而 8 则缺乏诗意,因为它无法用连续的正整数相加表示。

小蓝希望他面前的所有数字都蕴含诗意,为此,他决定从这 n 个数字中删除一部分。请问,小蓝需要删除多少个数字,才能使剩下的数字全部蕴含诗意?

输入格式

输入的第一行包含一个整数 n,表示展示的数字个数。

第二行包含 n 个整数 a1, a2, . . . , an,相邻整数之间使用一个空格分隔,表示展示的数字。

输出格式

输出一行包含一个整数,表示小蓝需要删除的数字个数,以使剩下的数字全部蕴含诗意。

样例输入

1
2
3
3 6 8

样例输出

1
1

提示

【样例说明】

在样例中,数字 3 可以表示为 1 + 2,数字 6 可以表示为 1 + 2 + 3,数字 8无法表示为连续的正整数相加,因此,需要删除的数字个数为 1。

【评测用例规模与约定】

对于 30% 的评测用例,1 ≤ n ≤ 103,1 ≤ ai ≤ 103。对于所有评测用例,1 ≤ n ≤ 2 × 105,1 ≤ ai ≤ 1016

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

int n,num;
int ans = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
for (int i = 0; i < n; i++)//能用for循环就for循环吧
{
cin >> num;
if (num % 2 != 0)//奇数一定是
ans++;
else {
int t = num;

for (int j = t / 2; j > 0; j--)//倒序枚举
{
int q = j;
int sum = 0;
while (sum < t)
{
sum += q;
q--;
}
if (sum == t)
{
ans++;
break;
}
}
}
}
cout << n - ans;
return 0;//必须要写
}

自己写的纯暴力,结果全部超时!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

int n,num;
int ans = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
for (int i = 0; i < n; i++)
{
cin >> num;
if (num % 2 != 0)//奇数一定是
ans++;
else if ((num & (num - 1)) != 0)//不是2的幂的偶数也一定是
ans++;
}
cout << n - ans;
return 0;//必须要写
}

我只发现了奇数一定可以由连续的数组成,然而不是2的幂的偶数也一定可以。

注意单个&是按位与。

如何判断一个数是否是2的幂


题目 3240: 封闭图形个数(sort函数自定义排序规则)

题目描述

在蓝桥王国,数字的大小不仅仅取决于它们的数值大小,还取决于它们所形成的“封闭图形”的个数。

封闭图形是指数字中完全封闭的空间,例如数字 1、2、3、5、7 都没有形成封闭图形,而数字 0、4、6、9 分别形成了 1 个封闭图形,数字 8 则形成了 2个封闭图形。值得注意的是,封闭图形的个数是可以累加的。例如,对于数字68,由于 6 形成了 1 个封闭图形,而 8 形成了 2 个,所以 68 形成的封闭图形的个数总共为 3。

在比较两个数的大小时,如果它们的封闭图形个数不同,那么封闭图形个数较多的数更大。例如,数字 41 和数字 18,它们对应的封闭图形的个数分别为 1 和 2,因此数字 41 小于数组 18。如果两个数的封闭图形个数相同,那么数值较大的数更大。例如,数字 14 和数字 41,它们的封闭图形的个数都是 1,但 14 < 41,所以数字 14 小于数字 41。如果两个数字的封闭图形个数和数值都相同,那么这两个数字被认为是相等的。

小蓝对蓝桥王国的数字大小规则十分感兴趣。现在,他将给定你 n 个数a1, a2, . . . , an,请你按照蓝桥王国的数字大小规则,将这 n 数从小到大排序,并输出排序后结果。

输入格式

输入的第一行包含一个整数 n ,表示给定的数字个数。

第二行包含 n 个整数 a1, a2, . . . , an ,相邻整数之间使用一个空格分隔,表示待排序的数字。

输出格式

输出一行包含 n 个整数,相邻整数之间使用一个空格分隔,表示按照蓝桥王国的数字大小规则从小到大排序后的结果。

样例输入

1
2
3
18 29 6

样例输出

1
6 29 18

提示

【样例说明】

对于给定的数字序列 [18, 29, 6],数字 18 的封闭图形个数为 2,数字 29 的封闭图形个数为 1,数字 6 的封闭图形个数为 1。按照封闭图形个数从小到大排序后,得到 [29, 6, 18]。由于数字 29 和数字 6 的封闭图形个数相同,因此需要进一步按照数值大小对它们进行排序,最终得到 [6, 29, 18]。

【评测用例规模与约定】

对于 50% 的评测用例,1 ≤ n ≤ 2 × 103,1 ≤ ai ≤ 105。对于所有评测用例,1 ≤ n ≤ 2 × 105,1 ≤ ai ≤ 109。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 2e5 + 10;
int st[10];
int n;
int num[N];

bool cmp(int a, int b)//自定义排序规则
{
int x = a, y = b;//避免改变形参的值
int sum1 = 0, sum2 = 0;
while (x != 0)
{
int t = x % 10;//取每个位
x /= 10;
sum1 += st[t];//逐位计算
}
while (y != 0)
{
int t = y % 10;// 取每个位
y /= 10;
sum2 += st[t];//逐位计算
}
//返回规则
if (sum1 == sum2)//记得是两个等号
{
return a < b;
}
return sum1 < sum2;
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

//存放每个数字具有的封闭空间个数
st[0] = 1;//别忘记了0
st[1] = 0;
st[2] = 0;
st[3] = 0;
st[4] = 1;
st[5] = 0;
st[6] = 1;
st[7] = 0;
st[8] = 2;
st[9] = 1;

cin >> n;
for (int i = 0; i < n; i++)//输入n个数
{
cin >> num[i];
}

sort(num, num + n, cmp);//排序
for (int i = 0; i < n; i++)//输出
{
cout << num[i] << " ";
}
return 0;//必须要写
}

MyAns

复习了sort函数自定义排序规则,100分!!!


题目 3242: 商品库存管理(一维差分+前缀和)

题目描述

在库存管理系统中,跟踪和调节商品库存量是关键任务之一。小蓝经营的仓库中存有多种商品,这些商品根据类别和规格被有序地分类并编号,编号范围从 1 至 n。初始时,每种商品的库存量均为 0。

为了高效地监控和调整库存量,小蓝的管理团队设计了 m 个操作,每个操作涉及到一个特定的商品区间,即一段连续的商品编号范围(例如区间 [L, R])。执行这些操作时,区间内每种商品的库存量都将增加 1。然而,在某些情况下,管理团队可能会决定不执行某些操作,使得这些操作涉及的商品区间内的库存量不会发生改变,维持原有的状态。

现在,管理团队需要一个评估机制,来确定如果某个操作未被执行,那么最终会有多少种商品的库存量为 0。对此,请你为管理团队计算出,对于每个操作,如果不执行该操作而执行其它操作,库存量为 0 的商品的种类数。

输入格式

输入的第一行包含两个整数 n 和 m,分别表示商品的种类数和操作的个数。接下来的 m 行,每行包含两个整数 L 和 R,表示一个操作涉及的商品区间。

输出格式

输出 m 行,每行一个整数,第 i 行的整数表示如果不执行第 i 个操作,则最终库存量为 0 的商品种类数。

样例输入

1
2
3
4
5 3
1 2
2 4
3 5

样例输出

1
2
3
1
0
1

提示

【样例说明】考虑不执行每个操作时,其余操作对商品库存的综合影响:

- 不执行操作 1:剩余的操作是操作 2(影响区间 [2, 4])和操作 3(影响区间 [3, 5])。执行这两个操作后,商品库存序列变为 [0, 1, 2, 2, 1]。在这种情况下,只有编号为 1 的商品的库存量为 0。因此,库存量为 0 的商品种类数为 1。

- 不执行操作 2:剩余的操作是操作 1(影响区间 [1, 2])和操作 3(影响区间 [3, 5])。执行这两个操作后,商品库存序列变为 [1, 1, 1, 1, 1]。在这种情况下,所有商品的库存量都不为 0。因此,库存量为 0 的商品种类数为 0。

- 不执行操作 3:剩余的操作是操作 1(影响区间 [1, 2])和操作 2(影响区间 [2, 4])。执行这两个操作后,商品库存序列变为 [1, 2, 1, 1, 0]。在这种情况下,只有编号为 5 的商品的库存量为 0。因此,库存量为 0 的商品种类数为 1。

【评测用例规模与约定】对于 20% 的评测用例,1 ≤ n, m ≤ 5 × 103,1 ≤ L ≤ R ≤ n。对于所有评测用例,1 ≤ n, m ≤ 3 × 105,1 ≤ L ≤ R ≤ n。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 3e5 + 10;
int n, m;
int a[N], d[N], p[N];
typedef pair<int, int> pii;
vector<pii> st;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> m;
st.resize(m + 1); // 预分配空间,防止 st[i] 访问越界
for (int i = 1; i <= m; i++)
{
int l, r;
cin >> l >> r;
st[i] = {l, r};

d[l]++;//差分数组
d[r + 1]--;
}

int sum = 0;
for (int i = 1; i <= n; i++)//恢复原数组
{
a[i] = a[i - 1] + d[i];
if (a[i] == 0)//统计商品为0的种类数
sum++;
}
for (int i = 1; i <= n; i++)//构造前缀和数组,用于存放前i个数里,1的个数
{
p[i] = p[i - 1] + (a[i] == 1);
}

for (int i = 1; i <= m; i++)//时间复杂度降为O(m)
{
int l = st[i].first;
int r = st[i].second;
cout << sum + p[r] - p[l - 1] << endl;
}

return 0;//必须要写
}

看到题目时就想到了用差分做,但是怎么也没想到还要使用前缀和

首先利用一维差分计算出进行了全部操作后各商品的种类数,接下来就是撤回某次操作,统计商品数为0的种类数

那么如何撤回呢?

1.统计所有商品种类数为0的个数。

2.统计每个前缀中商品种类数为1的个数。

3.撤回某个区间的操作,就是等于把这个区间内原本商品种类数为1的变为0,(大于1的不用管,它们减去1也变不成0,对答案没有影响)。

4.利用p[r] - p[l - 1]求出l到r之间商品种类数为1的个数。

5.将区间外商品种类数为0的个数加上区间内商品种类数为1的个数就是最终的答案。

奇怪的是,该解在洛谷可以AC,但是在dotcpp只能拿9分。

若不使用前缀和提前计算出每个前缀中商品种类数为1的个数,那么就需要遍历l到r这个区间m次,时间复杂度为O(m*n),必然会超时。


2023年第十四届蓝桥杯大赛软件类省赛C/C++大学A组真题

题目 3143: 更小的数(区间DP)

题目描述

蓝桥杯2023年第十四届省赛真题-更小的数

小蓝有一个长度均为 n 且仅由数字字符 0 ∼ 9 组成的字符串,下标从 0 到 n − 1,你可以将其视作是一个具有 n 位的十进制数字 num,小蓝可以从 num 中选出一段连续的子串并将子串进行反转,最多反转一次。小蓝想要将选出的子串进行反转后再放入原位置处得到的新的数字 numnew 满足条件 numnew < num,请你帮他计算下一共有多少种不同的子串选择方案,只要两个子串在 num 中的位置不完全相同我们就视作是不同的方案。

注意,我们允许前导零的存在,即数字的最高位可以是 0 ,这是合法的。

输入格式

输入一行包含一个长度为 n 的字符串表示 num(仅包含数字字符 0 ∼ 9),

从左至右下标依次为 0 ∼ n − 1。

输出格式

输出一行包含一个整数表示答案。

样例输入

1
210102

样例输出

1
8

提示

一共有 8 种不同的方案:

1)所选择的子串下标为 0 ∼ 1 ,反转后的 numnew = 120102 < 210102 ;

2)所选择的子串下标为 0 ∼ 2 ,反转后的 numnew = 012102 < 210102 ;

3)所选择的子串下标为 0 ∼ 3 ,反转后的 numnew = 101202 < 210102 ;

4)所选择的子串下标为 0 ∼ 4 ,反转后的 numnew = 010122 < 210102 ;

5)所选择的子串下标为 0 ∼ 5 ,反转后的 numnew = 201012 < 210102 ;

6)所选择的子串下标为 1 ∼ 2 ,反转后的 numnew = 201102 < 210102 ;

7)所选择的子串下标为 1 ∼ 4 ,反转后的 numnew = 201012 < 210102 ;

8)所选择的子串下标为 3 ∼ 4 ,反转后的 numnew = 210012 < 210102 ;

对于 20% 的评测用例,1 ≤ n ≤ 100 ;

对于 40% 的评测用例,1 ≤ n ≤ 1000 ;

对于所有评测用例,1 ≤ n ≤ 5000 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int n = 5010;
int f[n][n];//默认全为0
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

string s;
cin >> s;
int ans = 0;

//区间DP
for (int len = 2; len <= s.size(); len++)//子串长度为2、3、4、5...时1
{
for (int l = 0; l + len - 1 < s.size(); l++)//左右边界的下标l和r
{
int r = l + len - 1;//右边界下标
if (s[l] > s[r])
f[l][r] = 1;//表明这段连续子串是合法的
else if (s[l] == s[r])
f[l][r] = f[l + 1][r - 1];//状态转移,从较长子串的状态转移到较短子串的状态

ans += f[l][r];
}
}
cout << ans;

return 0;//必须要写
}

该题使用到了区间DP,好厉害的算法。


题目 3145: 买瓜(DFS+剪枝+贪心)

题目描述

小蓝正在一个瓜摊上买瓜。瓜摊上共有 n 个瓜,每个瓜的重量为 Ai 。

小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀

小蓝希望买到的瓜的重量的和恰好为 m 。

请问小蓝至少要劈多少个瓜才能买到重量恰好为 m 的瓜。如果无论怎样小蓝都无法得到总重恰好为 m 的瓜,请输出 −1

输入格式

输入的第一行包含两个整数 n, m,用一个空格分隔,分别表示瓜的个数和小蓝想买到的瓜的总重量。

第二行包含 n 个整数 Ai,相邻整数之间使用一个空格分隔,分别表示每个瓜的重量。

输出格式

输出一行包含一个整数表示答案。

样例输入

1
2
3 10
1 3 13

样例输出

1
2

提示

对于 20% 的评测用例,∑n≤10;

对于 60% 的评测用例,∑n≤20;

对于所有评测用例,1 ≤n≤30,1≤ Ai ≤ 1e9 ,1 ≤ m ≤ 1e9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 32;
int n, m;
int ans = INF;
int a[N];
int s[N];//后缀和数组

void dfs(int u, double w, int cnt)
{
//u:当前已经考虑了第u个瓜
//w:要买的瓜的总重量
//cnt:砍过的刀数
if (w == m)
{
ans = min(ans, cnt);//选个更小的
return;
}
//如果遍历过了所有的瓜,那就返回
if (u >= n)//dfs出口
return;

//剪枝策略
//如果当前答案已经不如最优解了,那就返回
if (cnt >= ans)
return;
//如果瓜的重量已经超了,那就返回
if (w >= m)
return;
//如果后面瓜的重量之和加上考虑过的,已经小于m了,那就返回
if (w + s[u] < m)
return;

//考虑下一个瓜
//不要这个瓜
dfs(u + 1, w, cnt);
//要,不砍
dfs(u + 1, w + a[u], cnt);
//要,砍一半
dfs(u + 1, w + a[u] / 2.0, cnt + 1);
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> m;
for (int i = 0; i < n; i++)//输入每个瓜的重量
{
cin >> a[i];
}

sort(a, a + n);
reverse(a, a + n);//对瓜的重量从大到小排序,贪心

for (int i = n - 1; i >= 0; i--)//存放后缀和,s[i]表示从i个瓜以后的重量之和,预处理。后续剪枝用
{
s[i] = s[i + 1] + a[i];
}

dfs(0, 0.0, 0);

if (ans == INF) ans = -1;
cout << ans << endl;

return 0;//必须要写
}

该题使用DFS+剪枝+贪心。

好好想想如何剪枝。


题目 3142: 平方差(打表)

题目描述

给定 L, R,问 L ≤ x ≤ R 中有多少个数 x 满足存在整数 y,z 使得 x = y2 − z2。

输入格式

输入一行包含两个整数 L, R,用一个空格分隔。

输出格式

输出一行包含一个整数满足题目给定条件的 x 的数量。

样例输入

1
1 5

样例输出

1
4

提示

1 = 1^2 − 0^2 ;

3 = 2^2 − 1^2 ;

4 = 2^2 − 0^2 ;

5 = 3^2 − 2^2 。

对于 40% 的评测用例,LR ≤ 5000 ;

对于所有评测用例,1 ≤ L ≤ R ≤ 1e9 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

//打表找规律,测试100以内的数据
void test() {
bool st[101] = {0};
for (int i = 1; i <= 100; i++) {
for (int j = 0; j <= 100; j++) {
for (int k = 0; k <= 100; k++) {
if (i == (j * j - k * k)) {
//if (i % 2) continue;//避开奇数
if (!st[i])//避免重复输出
cout << i << endl;
st[i] = true;
}
}
}
}
}

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

//test();
int l, r;
cin >> l >> r;
int One_to_R = ceil(r / 2.0) + r / 4;
int One_to_L_sub_1 = ceil((l - 1) / 2.0) + (l - 1) / 4;
cout << One_to_R - One_to_L_sub_1;//用1到R的答案减去1到L-1的答案,就是L到R的答案

return 0;//必须要写
}

该题使用打表法

注意到数据范围的边界为1e9,那就意味着即便使用O(n)的方法去做也无法通过全部测试样例。

先测试100以内的数据观察规律,可以发现数据都是奇数和4的倍数。

那么问题就转化为如何求出L到R之间,奇数和4的倍数的总数。


题目 3144: 颜色平衡树(DFS+哈希表)

题目描述

给定一棵树,结点由 1 至 n 编号,其中结点 1 是树根。树的每个点有一个颜色 Ci。

如果一棵树中存在的每种颜色的结点个数都相同,则我们称它是一棵颜色平衡树。

求出这棵树中有多少个子树是颜色平衡树。

输入格式

输入的第一行包含一个整数 n ,表示树的结点数。

接下来 n 行,每行包含两个整数 Ci , Fi,用一个空格分隔,表示第 i 个结点的颜色和父亲结点编号。

特别地,输入数据保证 F1 为 0 ,也即 1 号点没有父亲结点。保证输入数据是一棵树。

输出格式

输出一行包含一个整数表示答案。

样例输入

1
2
3
4
5
6
7
6
2 0
2 1
1 2
3 3
3 4
1 4

样例输出

1
4

提示

编号为 1, 3, 5, 6 的 4 个结点对应的子树为颜色平衡树。

对于 30% 的评测用例,n ≤ 200,Ci ≤ 200 ;

对于 60% 的评测用例,n ≤ 5000,Ci ≤ 5000 ;

对于所有评测用例,1 ≤ n ≤ 200000,1 ≤ Ci ≤ 200000,0 ≤ Fi < i 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 2e5 + 10;
int n, ans = 0;
vector<int> f_son[N];//存放每个节点的儿子节点
int color[N];//存放每个节点的颜色

//x表示当前遍历到了哪个节点
void dfs(int x, unordered_map<int, int>& mp)
{
if (!f_son[x].size())//深搜到了叶子节点
{
mp[color[x]]++;//先把当前节点的颜色数加一
ans++;//叶子节点一定是颜色平衡树,答案加一
return;//返回上一层
}

unordered_map<int, int> it;//新建一个哈希表,存放 颜色种类:颜色数
it[color[x]]++;//先把当前节点的颜色数加一

for (int i = 0; i < f_son[x].size(); i++)//遍历当前节点的所有儿子节点
{
dfs(f_son[x][i], it);//把当前节点的哈希表it传给儿子节点,便于后续上传颜色数信息
}

int pre = 0, flag = 1;//判断当前子树是否是颜色平衡树
for (auto i : it)
{
mp[i.first] += i.second;//把当前子树的哈希表信息上传给父亲节点
if (pre && pre != i.second) // 如果pre已经被赋值但是不与其他颜色数相等,则不是颜色平衡树
flag = 0;
pre = i.second;
}
if (flag)//颜色数都相同,说明该子树是颜色平衡树
ans++;
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
int f;
for (int i = 1; i <= n; i++) {//输入
cin >> color[i] >> f;
f_son[f].push_back(i);//存放每个节点的孩子节点
}

unordered_map<int, int> st;//根节点的哈希表,最终自下而上所有的颜色数信息都会汇总到这里
dfs(1, st);

cout << ans;
return 0;
}

该解是我现阶段唯一看得懂的答案(即使也花了些时间理解),没有用什么高深的算法,就是单纯的DFS遍历。

结合图一步步推导完了全流程,不禁感叹DFS的精妙,嗯,还得努力啊。

image-20250406211831192

2023年第十四届蓝桥杯大赛软件类省赛C/C++大学B组真题

题目 3150: 冶炼金属(暴力/二分)

题目描述

小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V,V 是一个正整数,这意味着消耗 V 个普通金属 O 恰好可以冶炼出一个特殊金属 X,当普通金属 O 的数目不足 V 时,无法继续冶炼

现在给出了 N 条冶炼记录,每条记录中包含两个整数 A 和 B,这表示本次投入了 A 个普通金属 O,最终冶炼出了 B 个特殊金属 X。每条记录都是独立的,这意味着上一次没消耗完的普通金属 O 不会累加到下一次的冶炼当中

根据这 N 条冶炼记录,请你推测出转换率 V 的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。

输入格式

第一行一个整数 N,表示冶炼记录的数目。

接下来输入 N 行,每行两个整数 A、B,含义如题目所述。

输出格式

输出两个整数,分别表示 V 可能的最小值和最大值,中间用空格分开。

1
2
3
4
3
75 3
53 2
59 2

样例输出

1
20 25

提示

当 V = 20 时,有:⌊75/20⌋ = 3,⌊ 53/20 ⌋ = 2,⌊ 59/20 ⌋ = 2,可以看到符合所有冶炼记录。

当 V = 25 时,有:⌊75/25⌋ = 3,⌊ 53/25 ⌋ = 2,⌊ 59/25 ⌋ = 2,可以看到符合所有冶炼记录。

且再也找不到比 20 更小或者比 25 更大的符合条件的 V 值了。

对于 30% 的评测用例,1 ≤ N ≤ 10^2。

对于 60% 的评测用例,1 ≤ N ≤ 10^3。

对于 100% 的评测用例,1 ≤ N ≤ 10^4,1 ≤ B ≤ A ≤ 10^9。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<bits/stdc++.h>
using namespace std;

int main()
{
int n;
cin >> n;
vector<int> a(n + 1), b(n + 1); // 初始化 a 和 b,大小为 n+1

for (int i = 1; i <= n; i++)//存入A,B值
cin >> a[i] >> b[i];
for (int i = 1; i <= 1e6; i++)//暴力枚举所有V(求min)
{
bool flag = 1;
for (int j = 1; j <= n; j++)//用当前的V测试所有样例
{
if (b[j] != a[j] / i)//如果出现一个情况不满足,那么这个V就不合法,找下一个V
{
flag = 0;
break;
}
}
if (flag == 1) //找到直接输出即最小的V
{
cout << i << " ";
break;
}
}
for (int i = 1e6; i >= 1; i--)//暴力枚举所有V(求max)
{
bool flag = 1;
for (int j = 1; j <= n; j++)
{
if (b[j] != a[j] / i)//如果出现一个情况不满足,那么这个V就不合法,找下一个V
{
flag = 0;
break;
}
}
if (flag == 1) //找到直接输出即最大的V
{
cout << i << " ";
break;
}
}
return 0;
}

该解使用暴力枚举,需要折中枚举值的范围。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> a, b; // 动态分配大小
bool check_min(int mid)
{
for (int i = 1; i <= n; i++)//用当前的V测试所有样例
{
if (b[i] < a[i] / mid)//如果V值很小
return 0;//如果有任何一组不合法,就返回0
}
return 1;//b[i] >= a[i] / V (所有的样例都满足,说明V值很大)
}
bool check_max(int mid)
{
for (int i = 1; i <= n; i++)//用当前的V测试所有样例
{
if (b[i] > a[i] / mid)//如果V值很大
return 0;//如果有任何一组不合法,就返回0
}
return 1;//b[i] <= a[i] / V(所有的样例都满足,说明V值很小)
}

int main()
{
cin >> n;
a.resize(n + 1); // 动态分配大小
b.resize(n + 1);

for (int i = 1; i <= n; i++)//存入A,B值
cin >> a[i] >> b[i];

//找最小值V
int l = 1, r = 1e9;
while (l < r)
{
int mid = (l + r) / 2;
if (check_min(mid))
r = mid;
else l = mid + 1;
}
cout << l <<" ";

//找最大值V
l = 1, r = 1e9;
while (l < r)
{
int mid = (l + r + 1) / 2;//避免死循环
if (check_max(mid))
l = mid;
else r = mid - 1;
}
cout << l << " ";

return 0;
}

该解使用二分答案法,要记住对应的模板。

image-20250301132745983

当mid落入合法区域时,V值正好,满足b[i] = a[i] / mid

当mid落入合法区域的右边时,V值过大,满足b[i] > a[i] / mid

当mid落入合法区域的左边时,V值过小,满足b[i] < a[i] / mid

关于找最大值时mid = (l + r + 1) / 2的解释:

场景 1:找最大值

  • 在找最大值时,如果 mid 满足条件,我们希望 l 向右移动,即 l = mid
  • 如果使用 mid = (l + r) / 2,当 lr 相邻时,mid 会偏向左侧,导致 l 无法更新,陷入死循环。
  • 使用 mid = (l + r + 1) / 2,可以确保 mid 偏向右侧,避免死循环。

场景 2:找最小值

  • 在找最小值时,如果 mid 满足条件,我们希望 r 向左移动,即 r = mid
  • 使用 mid = (l + r) / 2 是安全的,因为 r 会不断向左移动,不会陷入死循环。

题目 3154: 子串简写(暴力/二分)

题目描述

程序猿圈子里正在流行一种很新的简写方法:对于一个字符串,只保留首尾字符,将首尾字符之间的所有字符用这部分的长度代替。例如 internation-alization 简写成 i18n,Kubernetes (注意连字符不是字符串的一部分)简写成 K8s, Lanqiao 简写成 L5o 等。

在本题中,我们规定长度大于等于 K 的字符串都可以采用这种简写方法(长度小于 K 的字符串不配使用这种简写)。

给定一个字符串 S 和两个字符 c1 和 c2,请你计算 S 有多少个以 c1 开头c2 结尾的子串可以采用这种简写?

输入格式

第一行包含一个整数 K。

第二行包含一个字符串 S 和两个字符 c1 和 c2。

输出格式

一个整数代表答案。

样例输入

1
2
4
abababdb a b

样例输出

1
6

提示

符合条件的子串如下所示,中括号内是该子串:

[abab]abdb

[ababab]db

[abababdb]

ab[abab]db

ab[ababdb]

abab[abdb]

对于 20% 的数据,2 ≤ K ≤ |S | ≤ 10000。

对于 100% 的数据,2 ≤ K ≤ |S | ≤ 5 × 1e5。S 只包含小写字母。c1 和 c2 都是小写字母。

|S | 代表字符串 S 的长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

int k, sum = 0;
string s;
char c1, c2;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> k;
cin >> s >> c1 >> c2;
for (int i = 0; i < s.size(); i++)
{
if (s[i] != c1) continue;
for (int j = i + 1; j < s.size(); j++)
{
if (s[j] == c2 && j - i + 1 >= k)
sum++;
}
}
cout << sum;

return 0;//必须要写
}

该解使用暴力枚举,两层for循环,时间复杂度为O(n^2),必然会超时。

只能拿到24分。。。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <bits/stdc++.h>
using namespace std;
typedef long long ll; // 使用 typedef 代替 #define

ll k, sum = 0;
string s;
char c1, c2;

int main() { // main 函数的返回类型必须是 int
cin >> k;
cin >> s >> c1 >> c2;

vector<int> c1_positions; // 存储 c1 的位置(下标)
for (int i = 0; i < s.size(); i++) {
if (s[i] == c1)
c1_positions.push_back(i);
}

ll ans = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] == c2) {
if (i - k + 1 < 0 || c1_positions.empty())//i-k+1是满足条件的子串的起始位置的下标,从这里开始到c2的长度等于K
continue; //i+1<k,说明长度不满足K,直接跳过就好

// 二分查找满足条件的 c1 的数量
int l = 0, r = (int)c1_positions.size() - 1; // 避免无符号整数溢出
while (l < r) {
int mid = (l + r + 1) / 2;
if (c1_positions[mid] <= (i - k + 1))
l = mid;
else
r = mid - 1;
}
if (c1_positions[l] <= i - k + 1) // 找到满足条件的字符串:以 c2 结尾,开头是从右向左的第一个 c1
ans += (l + 1); // 加1,数组下标比实际少1
}
}
cout << ans << endl;
return 0; // main 函数返回 0
}

cgi-bin_mmwebwx-bin_webwxgetmsgimg_&MsgID=2011104838485191816&skey=@crypt_7e2fd656_5f47741aa4bcbd9bdb6fe00eb39ecae3&mmweb_appid=wx_webfilehelper

cgi-bin_mmwebwx-bin_webwxgetmsgimg_&MsgID=9215831454549671306&skey=@crypt_7e2fd656_5f47741aa4bcbd9bdb6fe00eb39ecae3&mmweb_appid=wx_webfilehelper

该解使用二分法,挺抽象的,一开始理解不了i - k + 1的含义,好难。。。

我总觉得这个题解写得不够好,太乱了。


题目 3151: 飞机降落

题目描述

N 架飞机准备降落到某个只有一条跑道的机场。其中第 i 架飞机在 Ti 时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 Di 个单位时间,即它最早

可以于 Ti 时刻开始降落,最晚可以于 Ti + Di 时刻开始降落。降落过程需要 Li个单位时间。

一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。

请你判断 N 架飞机是否可以全部安全降落。

输入格式

输入包含多组数据。

第一行包含一个整数 T,代表测试数据的组数。

对于每组数据,第一行包含一个整数 N。

以下 N 行,每行包含三个整数:Ti,Di 和 Li。

输出格式

对于每组数据,输出 YES 或者 NO,代表是否可以全部安全降落。

样例输入

1
2
3
4
5
6
7
8
9
2
3
0 100 10
10 10 10
0 2 20
3
0 10 20
10 10 20
20 10 20

样例输出

1
2
YES
NO

提示

对于第一组数据,可以安排第 3 架飞机于 0 时刻开始降落,20 时刻完成降落。安排第 2 架飞机于 20 时刻开始降落,30 时刻完成降落。安排第 1 架飞机于 30 时刻开始降落,40 时刻完成降落。

对于第二组数据,无论如何安排,都会有飞机不能及时降落。

对于 30% 的数据,N ≤ 2。

对于 100% 的数据,1 ≤ T ≤ 10,1 ≤ N ≤ 10,0 ≤ Ti , Di , Li ≤ 105。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <bits/stdc++.h>
using namespace std;

int T, N;
bool st[20]; // 判断对应飞机是否降落
struct plane
{
int Ti, Di, Li;
} p[20];

// u表示已经有u架飞机成功降落了
// time表示当前的时间,前一架飞机落地的时间
bool dfs(int u, int time)
{
if (u >= N) return true; // 如果所有飞机都成功降落,返回true。(dfs出口)

// 考虑第(u + 1)架飞机谁落
for (int i = 0; i < N; i++)
{
if (!st[i]) // 如果第i架飞机未降落
{
st[i] = 1; // 标记为已降落
if (p[i].Ti + p[i].Di >= time) // 如果当前飞机的最晚降落时间不早于当前时间
{
int t = max(time, p[i].Ti) + p[i].Li; // 计算当前飞机的降落时间
if (dfs(u + 1, t)) return true; // 递归尝试下一架飞机
}
st[i] = 0; // 回溯,取消当前飞机的降落标记
}
}
return false; // 如果没有找到可行的降落顺序,返回false
}

int main()
{
cin >> T;
while (T--) // 输入数据
{
cin >> N; // 飞机个数
for (int j = 0; j < N; j++)
{
cin >> p[j].Ti >> p[j].Di >> p[j].Li; // 输入每架飞机的信息
}
if (dfs(0, 0)) // 调用DFS函数
cout << "YES" << endl;
else
cout << "NO" << endl;

// 重置st数组
for (int i = 0; i < N; i++)
st[i] = 0;
}
return 0;
}

该解使用DFS枚举,原本的题解看不太懂,经过AI改进后变得清楚很多,太强了。


题目 3156: 景区导游

题目描述

某景区一共有 N 个景点,编号 1 到 N。景点之间共有 N − 1 条双向的摆渡车线路相连,形成一棵树状结构。在景点之间往返只能通过这些摆渡车进行,需要花费一定的时间。

小明是这个景区的资深导游,他每天都要按固定顺序带客人游览其中 K 个景点:A1, A2, . . . , AK。今天由于时间原因,小明决定跳过其中一个景点,只带游客按顺序游览其中 K − 1 个景点。具体来说,如果小明选择跳过 Ai,那么他会按顺序带游客游览 A1, A2, . . . , Ai−1, Ai+1, . . . , AK, (1 ≤ i ≤ K)。

请你对任意一个 Ai,计算如果跳过这个景点,小明需要花费多少时间在景点之间的摆渡车上?

输入格式

第一行包含 2 个整数 N 和 K。

以下 N − 1 行,每行包含 3 个整数 u, v 和 t,代表景点 u 和 v 之间有摆渡车线路,花费 t 个单位时间。

最后一行包含 K 个整数 A1, A2, . . . , AK 代表原定游览线路。

输出格式

输出 K 个整数,其中第 i 个代表跳过 Ai 之后,花费在摆渡车上的时间。

样例输入

1
2
3
4
5
6
7
6 4
1 2 1
1 3 1
3 4 2
3 5 2
4 6 3
2 6 5 1

样例输出

1
10 7 13 14

提示

原路线是 2 → 6 → 5 → 1。

当跳过 2 时,路线是 6 → 5 → 1,其中 6 → 5 花费时间 3 + 2 + 2 = 7,5 → 1 花费时间 2 + 1 = 3,总时间花费 10。

当跳过 6 时,路线是 2 → 5 → 1,其中 2 → 5 花费时间 1 + 1 + 2 = 4,5 → 1 花费时间 2 + 1 = 3,总时间花费 7。

当跳过 5 时,路线是 2 → 6 → 1,其中 2 → 6 花费时间 1 + 1 + 2 + 3 = 7,6 → 1 花费时间 3 + 2 + 1 = 6,总时间花费 13。

当跳过 1 时,路线时 2 → 6 → 5,其中 2 → 6 花费时间 1 + 1 + 2 + 3 = 7,6 → 5 花费时间 3 + 2 + 2 = 7,总时间花费 14。

对于 20% 的数据,2 ≤ K ≤ N ≤ 102。

对于 40% 的数据,2 ≤ K ≤ N ≤ 104。

对于 100% 的数据,2 ≤ K ≤ N ≤ 105,1 ≤ u, v, Ai ≤ N,1 ≤ t ≤ 105。保证Ai 两两不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long

const int N = 2e5 + 10;
typedef pair<int, int> pii;
map<pii, int> st;//记录{x, y}的距离是多少
int a[N];
vector<pii> edge[N];//存图

//s表示你要求的路径的起点
//u表示你当前走到了哪个点
//father表示你当前这个点的父亲节点是谁。避免重复走造成死循环
//v表示你要求的路径的终点
//sum表示从s走到u的路径花费总和。
bool dfs(int s, int u, int father, int v, int sum)
{
if (u == v)
{
st[{s, v}] = sum;
st[{v, s}] = sum;
return true;
}
for (int i = 0; i < edge[u].size(); i++)
{
int son = edge[u][i].first;//u点的邻接点
if (son == father)
continue;
int w = edge[u][i].second;//邻边的权值
if (dfs(s, son, u, v, sum + w))
return true;
}
return false;//如果没有找到路径,返回false;
}

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

int n, k;
cin >> n >> k;
for (int i = 0; i < n - 1; i++)//存图
{
int x, y, t;
cin >> x >> y >> t;
edge[x].push_back({y, t});//无向边
edge[y].push_back({x, t});
}
for (int i = 0; i < k; i++)//输入原本要参观的k个景点
cin >> a[i];

//求出按顺序走完整路线的总花费时间
int ans = 0;
for (int i = 0; i < k - 1; i++)
{
dfs(a[i], a[i], -1, a[i + 1], 0);
ans += st[{a[i], a[i + 1]}];
//cout << ans << endl;
}
//裁剪
for (int i = 0; i < k; i++)
{
int tmp = ans;
if (i == 0)//如果去除第1个点
tmp -= st[{a[i], a[i + 1]}];
else if (i == k - 1)//如果去除最后1个点
tmp -= st[{a[i - 1], a[i]}];
else //如果去除中间的点
{
tmp -= st[{a[i - 1], a[i]}];
tmp -= st[{a[i], a[i + 1]}];
dfs(a[i - 1], a[i - 1], -1, a[i + 1], 0);//记录该点前一个点到该点后一个点的边权
tmp += st[{a[i - 1], a[i + 1]}];
}
cout << tmp << " ";
}

return 0;//必须要写
}

该解使用dfs暴力遍历,只能拿43分。

从该题学会了写算法题的基本模板。


题目 3157: 砍树

题目描述

给定一棵由 n 个结点组成的树以及 m 个不重复的无序数对 (a1, b1), (a2, b2),

. . . , (am, bm),其中 ai 互不相同,bi 互不相同,ai ≠ bj(1 ≤ i, j ≤ m)。

小明想知道是否能够选择一条树上的边砍断,使得对于每个 (ai , bi) 满足 ai和 bi 不连通,如果可以则输出应该断掉的边的编号(编号按输入顺序从 1 开始),否则输出 -1.

输入格式

输入共 n + m 行,第一行为两个正整数 n,m。

后面 n − 1 行,每行两个正整数 xi,yi 表示第 i 条边的两个端点。

后面 m 行,每行两个正整数 ai,bi。

输出格式

一行一个整数,表示答案,如有多个答案,输出编号最大的一个

样例输入

1
2
3
4
5
6
7
8
6 2
1 2
2 3
4 3
2 5
6 5
3 6
4 5

样例输出

1
4

提示

断开第 2 条边后形成两个连通块:{3, 4},{1, 2, 5, 6},满足 3 和 6 不连通,4 和 5 不连通。

断开第 4 条边后形成两个连通块:{1, 2, 3, 4},{5, 6},同样满足 3 和 6 不连通,4 和 5 不连通。

4 编号更大,因此答案为 4。

对于 30% 的数据,保证 1 < n ≤ 1000。

对于 100% 的数据,保证 1 < n ≤ 1e5,1 ≤ m ≤ 2/n。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long

const int N = 2e5 + 10;
typedef pair<int, int> pii;
map<pii, int> id;//记录{x, y}的距离是多少
int w[N];//存放每个边的权值,初始为0
vector<int> edge[N];//存图

//s表示你要求的路径的起点
//u表示你当前走到了哪个点
//father表示你当前这个点的父亲节点是谁。避免重复走造成死循环
//v表示你要求的路径的终点
bool dfs(int s, int u, int father, int v)
{
if (u == v)//找到终点
return true;
for (int i = 0; i < edge[u].size(); i++)
{
int son = edge[u][i];//避免重复走造成死循环
if (son == father)
continue;
if (dfs(s, son, u, v))
{
int ID = id[{u, son}];//找到对应边的边号
w[ID]++;
return true;
}
}
return false;
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

int n, m;
cin >> n >> m;
for (int i = 0; i < n - 1; i++)//存图
{
int x, y;
cin >> x >> y;
edge[x].push_back(y);//无向边
edge[y].push_back(x);
id[{x, y}] = id[{y, x}] = i;//设置边号
}
for (int i = 0; i < m; i++)//dfs遍历每条边,并赋予权值
{
int x, y;
cin >> x >> y;
dfs(x, x, -1, y);
}
int ans = 0;
for (int i = n - 2; i >= 0; i--)//从最大编号的边开始,符合题意
{
if (w[i] == m)
{
ans = i + 1;//边号从1开始,数组下标要加一
break;
}
}
cout << ans;
return 0;//必须要写
}

该解使用dfs遍历,和上一题类似,但本质上还是暴力,只能拿33分。

感觉对dfs的理解更深刻了。


题目 3155: 整数删除

题目描述

给定一个长度为 N 的整数数列:A1, A2, . . . , AN。你要重复以下操作 K 次:

每次选择数列中最小的整数(如果最小值不止一个,选择最靠前的),将其删除。并把与它相邻的整数加上被删除的数值。输出 K 次操作后的序列。

输入格式

第一行包含两个整数 N 和 K。

第二行包含 N 个整数,A1, A2, A3, . . . , AN。

输出格式

输出 N − K 个整数,中间用一个空格隔开,代表 K 次操作后的序列。

样例输入

1
2
5 3
1 4 2 8 7

样例输出

1
17 7

提示

数列变化如下,中括号里的数是当次操作中被选择的数:

[1] 4 2 8 7

5 [2] 8 7

[7] 10 7

17 7

对于 20% 的数据,1 ≤ K < N ≤ 10000。

对于 100% 的数据,1 ≤ K < N ≤ 5 × 105,0 ≤ Ai ≤ 108。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 5e5 + 10;
int n, k, pos = 0;
int a[N];//存储数字
int st[N];//标记某个数字是否被删除
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> k;
for (int i = 0; i < n; i++)//存储n个数字
cin >> a[i];

while (k)
{
int MIN = INF;//每一轮重置最小值
for (int i = 0; i < n; i++)//找到每一轮的最小数
{
if (a[i] < MIN && !st[i])//该数更小且未被删除过
{
MIN = a[i];
pos = i;
}
}
st[pos] = 1;//标记该数为删除状态

//对相邻的数字进行操作
for (int i = pos - 1; i >= 0; i--)//从该数左边开始遍历
{
if (!st[i])//如果该数未删除过
{
a[i] += MIN;
break;//找到一个就跳出
}

}

for (int i = pos + 1; i < n; i++)//从该数右边开始遍历
{
if (!st[i])//如果该数未删除过
{
a[i] += MIN;
break;//找到一个就跳出
}
}
k--;
}

for (int i = 0; i < n; i++)
{
if (!st[i])
cout << a[i] << " ";
}

return 0;//必须要写
}

该解使用暴力求解,只能得24分。。。

只定义了两个数组和多个for循环。

#define INF 0x3f3f3f3f3f3f3f3f 定义无穷大,以后最大值就这么写,分数会高不少(原来只有5分)。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long

typedef pair<int, int> pii;
const int N = 5e5 + 10;
int n, k;
int a[N], l[N], r[N], st[N];
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);
cin >> n >> k;

priority_queue<pii, vector<pii>, greater<pii>> q;//优先队列(小根堆)

for (int i = 0; i < n; i++)
{
cin >> a[i];//将n个数存入数组a[N]中
q.push({ a[i],i });//将数值和下标存入堆中(真正发生删除操作的地方)
st[i] = a[i];//备份数组a[N],存放最新的数字
l[i] = i - 1;//获取该数左边的数的下标
r[i] = i + 1;//获取该数右边的数的下标
if (r[i] == n)//表示当前数字是最后一个数
r[i] = -1;
}

int cnt = k;
while (k)
{
pii t = q.top();//获取最小数和其下标
q.pop();//删除最小数
if (t.first != st[t.second])//更新堆顶元素(这里要好好想想) 先删除,再加入
{
q.push({ st[t.second],t.second });
continue;
}

int pos = t.second;//获取最小数的下标
//将该元素的相邻元素加上该数值
if (l[pos] >= 0)
st[l[pos]] += t.first;
if (r[pos] >= 0)
st[r[pos]] += t.first;
//更新相邻点的相邻元素(模拟链表删除元素)
if (l[pos] >= 0)
r[l[pos]] = r[pos];
if (r[pos] >= 0)
l[r[pos]] = l[pos];

st[pos] = -1;//标记已经删除的元素
k--;//必须这里减
}
//输出
for (int i = 0; i < n; i++)
{
if (st[i] != -1)
cout << st[i] << " ";
}

return 0;//必须要写
}

该解使用到了模拟链表+优先队列(小根堆)

最夸张的是定义了4个数组。。。


题目 3152: 接龙数列

题目描述

对于一个长度为 K 的整数数列:A1, A2, . . . , AK,我们称之为接龙数列当且仅当 Ai 的首位数字恰好等于 Ai−1 的末位数字 (2 ≤ i ≤ K)。

例如 12, 23, 35, 56, 61, 11 是接龙数列;12, 23, 34, 56 不是接龙数列,因为 56的首位数字不等于 34 的末位数字。所有长度为 1 的整数数列都是接龙数列。

现在给定一个长度为 N 的数列 A1, A2, . . . , AN,请你计算最少从中删除多少个数,可以使剩下的序列是接龙序列?

输入格式

第一行包含一个整数 N。

第二行包含 N 个整数 A1, A2, . . . , AN。

输出格式

一个整数代表答案。

样例输入

1
2
5
11 121 22 12 2023

样例输出

1
1

提示

删除 22,剩余 11, 121, 12, 2023 是接龙数列。

对于 20% 的数据,1 ≤ N ≤ 20。

对于 50% 的数据,1 ≤ N ≤ 10000。

对于 100% 的数据,1 ≤ N ≤ 105,1 ≤ Ai ≤ 109。所有 Ai 保证不包含前导 0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 + 10;
int n, ans = 0;
int a[N];

int get_first(int x)//获得最高位数字
{
int num = 0;
while(x)
{
num = x % 10;//获得最低位
x /= 10;//去除最低位
}
return num;
}
int get_last(int x)//获得最低位数字
{
return x % 10;
}

//u表示:当前考虑到了第几个数字,以下标形式表示
//last表示:方案中已经选了的数列最后一个数字是多少
//cnt表示:当前方案中一共有多少个数字
void dfs(int u, int last, int cnt)
{
if (u >= n)//dfs出口
{
ans = max(ans, cnt);
return;
}
if (n - u + cnt <= ans)//优化(没有也行,有了更好)
{
return;
}

//第u个数是否选,如果选这个数字
//就必须和前面最后一个数字构成接龙序列。
if (get_last(last) == get_first(a[u])||last == -1)//或者该数为数列的第一个数,第一次第一个数必定选
dfs(u + 1, a[u], cnt + 1);//那么考虑下一个数字

//从第一个数开始的情况结束后,会返回到这里继续执行。这一次不选第一个数,必定选第二个数
//第u个数不选,考虑下一个数(始终都是向右遍历)
dfs(u + 1, last, cnt);
}

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
dfs(0, -1, 0);
cout << n - ans << endl;//计算保留最多的数字,再用n减去

return 0;//必须要写
}

该解使用dfs暴力枚举,只能得27分。。。

dfs函数的过程挺烧脑的。


2023年第十四届蓝桥杯大赛软件类省赛C/C++大学C组真题

题目 3158: 三国游戏(贪心)

小蓝正在玩一款游戏。游戏中魏蜀吴三个国家各自拥有一定数量的士兵X, Y, Z (一开始可以认为都为 0 )。游戏有 n 个可能会发生的事件,每个事件之间相互独立且最多只会发生一次,当第 i 个事件发生时会分别让 X, Y, Z 增加Ai , Bi ,Ci 。

当游戏结束时 (所有事件的发生与否已经确定),如果 X, Y, Z 的其中一个大于另外两个之和,我们认为其获胜。例如,当 X > Y + Z 时,我们认为魏国获胜。小蓝想知道游戏结束时如果有其中一个国家获胜,最多发生了多少个事件?

如果不存在任何能让某国获胜的情况,请输出 −1

输入格式

输入的第一行包含一个整数 n 。

第二行包含 n 个整数表示 Ai,相邻整数之间使用一个空格分隔。

第三行包含 n 个整数表示 Bi,相邻整数之间使用一个空格分隔。

第四行包含 n 个整数表示 Ci,相邻整数之间使用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。

样例输入

1
2
3
4
3
1 2 2
2 3 2
1 0 7

样例输出

1
2

提示

发生两个事件时,有两种不同的情况会出现获胜方。

发生 1, 2 事件时蜀国获胜。

发生 1, 3 事件时吴国获胜。

对于 40% 的评测用例,n ≤ 500 ;

对于 70% 的评测用例,n ≤ 5000 ;

对于所有评测用例,1 ≤ n ≤ 105,1 ≤ Ai , Bi ,Ci ≤ 109 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

int n;

int cal_ans(const vector<int>& a, const vector<int>& b, const vector<int>& c)//计算x,y,z各自胜利时最多发生的事件数
{
vector<int> tmp(n);
int sum = 0, cnt = 0;

for (int i = 0; i < n; i++)
tmp[i] = a[i] - (b[i] + c[i]);

sort(tmp.begin(), tmp.end());//从小到大排序
for (int i = n - 1; i >= 0; i--)//从最大的开始加,让a持续地保持赢的状态,直到 sum + tmp[i] <= 0 ,跳出循环
{
if (sum + tmp[i] > 0)
{
sum += tmp[i];
cnt++;
}
else break;
}
return cnt;
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
vector<int> a(n), b(n), c(n);
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < n; i++)
cin >> b[i];
for (int i = 0; i < n; i++)
cin >> c[i];


int a_ans = cal_ans(a, b, c);//a赢
int b_ans = cal_ans(b, a, c);//b赢
int c_ans = cal_ans(c, a, b);//c赢

int ans = max({ a_ans, b_ans, c_ans });
if (ans > 0)
cout << ans;
else cout << -1;

return 0;//必须要写
}

该解使用贪心法,将题意抽象成不等式,排序后从大到小相加,直到不大于0。


题目 3159: 填充(暴力+贪心)

题目描述

有一个长度为 n 的 01 串,其中有一些位置标记为 ?,这些位置上可以任意填充 0 或者 1,请问如何填充这些位置使得这个 01 串中出现互不重叠的 00 和 11 子串最多,输出子串个数。

输入格式

输入一行包含一个字符串。

输出格式

输出一行包含一个整数表示答案。

样例输入

1
1110?0

样例输出

1
2

提示

如果在问号处填 0 ,则最多出现一个 00 和一个 11:111000 。

对于所有评测用例,1 ≤ n ≤ 1000000 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

string s;
int cnt = 0;

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> s;
vector<bool> st(s.size());//标记数组

for (int i = 1; i < s.size(); i++)//直接从第二个数开始
{
if (!st[i - 1] && !st[i])//如果前两个都没被标记
{
if (s[i - 1] == s[i])//如果二者相同(包括问号)
{
st[i - 1] = st[i] = 1;
cnt++;
}
else if (s[i - 1] == '?' || s[i] == '?')//如果二者一个为?另一个为0或1
{
st[i - 1] = st[i] = 1;
cnt++;
}
else continue;
}
}
cout << cnt;

return 0;//必须要写
}

该解使用贪心法,让每一位字符始终与前面一个进行匹配,利用st数组对每一个字符进行标记。


题目 3160: 翻转(暴力+贪心)

题目描述

小蓝用黑白棋的 n 个棋子排成了一行,他在脑海里想象出了一个长度为 n 的 01 串 T,他发现如果把黑棋当做 1,白棋当做 0,这一行棋子也是一个长度为 n 的 01 串 S。

小蓝决定,如果在 S 中发现一个棋子和它两边的棋子都不一样,就可以将其翻转变成另一个颜色。也就是说,如果 S 中存在子串 101 或者 010,就可以选择将其分别变为 111 和 000,这样的操作可以无限重复。

小蓝想知道最少翻转多少次可以把 S 变成和 T 一模一样。

输入格式

输入包含多组数据。

输入的第一行包含一个正整数 D 表示数据组数。

后面 2D 行每行包含一个 01 串,每两行为一组数据,第 2i − 1 行为第 i 组

数据的 Ti,第 2i 行为第 i 组数据的 Si,Si 和 Ti 长度均为 ni。

输出格式

对于每组数据,输出一行包含一个整数,表示答案,如果答案不存在请输出 −1。

样例输入

1
2
3
4
5
2
1000111
1010101
01000
11000

样例输出

1
2
2
-1

提示

对于 20% 的评测用例,1 ≤∑D1 ni ≤ 10 ;
对于所有评测用例,保证 1 ≤∑D1 ni ≤ 106 ,ni > 0 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

string t, s;
int d;

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> d;
while (d)
{
int cnt = 0;
cin >> t >> s;
int n = t.size();
for (int i = 0; i < n; i++)//从左向右
{
if (s[i] == t[i])
continue;
//之后的情况都是s[i] != t[i]
if (i == 0 || i == n - 1)//首尾不等,再怎么翻转也没用,直接跳出
{
break;
}
if(s[i - 1] == s[i + 1] && s[i] != s[i + 1])//中间不等,符合条件时进行翻转
{
s[i] = s[i + 1];
cnt++;
}
if (s[i - 1] != s[i + 1])//中间不等,不符合条件无法翻转,直接跳出,不用再判断后面的元素
{
break;
}

}
if (s == t)//最终检验条件
cout << cnt << endl;
else
cout << -1 << endl;

d--;
}

return 0;//必须要写
}

该解是用贪心法,从左向右依次判断。

使用多if语句是本题的核心,最终检验的条件为s==t。


题目 3164: 公因数匹配

题目描述

给定 n 个正整数 Ai,请找出两个数 i, j 使得 i < j 且 Ai 和 Aj 存在大于 1 的公因数

如果存在多组 i, j,请输出 i 最小的那组。如果仍然存在多组 i, j,请输出 i 最小的所有方案中 j 最小的那组。

输入格式

输入的第一行包含一个整数 n。

第二行包含 n 个整数分别表示 A1 A2 · · · An,相邻整数之间使用一个空格分隔。

输出格式

输出一行包含两个整数分别表示题目要求的 i, j,用一个空格分隔。

样例输入

1
2
5
5 3 2 6 9

样例输出

1
2 4

提示

对于 40% 的评测用例,n ≤ 5000 ;

对于所有评测用例,1 ≤ n ≤ 105,1 ≤ Ai ≤ 106 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

map<int, vector<int>> st;
int cnt = 0;

void prim(int x, int pos)//分解质因数,将该数的位置存放在st中
{
for (int i = 2; i <= x / i; i++)
{
if (x % i)
continue;
st[i].push_back(pos);
while (x % i == 0)
x /= i;
}
if (x > 1)
st[x].push_back(pos);
}

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
prim(x, i);//传入该数和其位置,分解质因数
}

pair<int, int> ans = { INF,INF };
for (auto [x, y] : st)//按键值从小到大遍历st
{
if (y.size() < 2)
continue;
if (y[0] < ans.first)
ans = { y[0],y[1] };
if(y[0] == ans.first && y[1] < ans.second)
ans = { y[0],y[1] };
}
cout << ans.first << " " << ans.second << endl;

return 0;//必须要写
}

该解用到了算数基本定理:任何一个大于1的自然数 N,如果 N 不为质数,那么 N 可以唯一分解成有限个质数的乘积。

使用了map,pair数对。


2022年第十三届蓝桥杯大赛软件类省赛C/C++大学A组真题

题目 2664: 求和(暴力/后缀和)

题目描述

给定 n 个整数 a1, a2, · · · , an ,求它们两两相乘再相加的和,即 S = a1 · a2 + a1 · a3 + · · · + a1 · an + a2 · a3 + · · · + an-2 · an-1 + an-2 · an + an-1 · an.

输入格式

输入的第一行包含一个整数 n 。

第二行包含 n 个整数 a1, a2, · · · an。

输出格式

输出一个整数 S,表示所求的和。请使用合适的数据类型进行运算。

样例输入

1
2
4
1 3 6 9

样例输出

1
117

提示

对于 30% 的数据,1 ≤ n ≤ 1000,1 ≤ ai ≤ 100。

对于所有评测用例,1 ≤ n ≤ 200000,1 ≤ ai ≤ 1000。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 2e5 + 10;
int n;
int a[N];
int ans = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
for (int i = 0; i < n; i++)//存数
{
cin >> a[i];
}

for (int i = 0; i < n; i++)//两层for循环
{
for (int j = i + 1; j < n; j++)//j = i + 1
{
ans += a[i] * a[j];
}
}

cout << ans;
return 0;//必须要写
}

MyAns

使用两层for循环的暴力枚举,时间复杂度为n^2,竟然能得91分。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 2e5 + 10;
int n;
int a[N],s[N];
int ans = 0, sum = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
for (int i = 0; i < n; i++)//存数
{
cin >> a[i];
}

for (int i = n - 1; i >= 0; i--)//构建后缀和数组
{
s[i] = s[i + 1] + a[i];
}

for (int i = 0; i < n - 1; i++)
{
ans += a[i] * s[i + 1];//改进,时间复杂度变为O(n)
}

cout << ans;
return 0;//必须要写
}

100分

后面回顾的时候突然发现可以使用后缀和,自己很快就敲了出来,好开心!


题目 2665: 选数异或(暴力)

题目描述

给定一个长度为 n 的数列 A1, A2, · · · , An 和一个非负整数 x,给定 m 次查询, 每次询问能否从某个区间 [l,r] 中选择两个数使得他们的异或等于 x 。(按位异或

输入格式

输入的第一行包含三个整数 n, m, x 。

第二行包含 n 个整数 A1, A2, · · · , An 。

接下来 m 行,每行包含两个整数 li ,ri 表示询问区间 [li ,ri ] 。

输出格式

对于每个询问, 如果该区间内存在两个数的异或为 x 则输出 yes, 否则输出 no。

样例输入

1
2
3
4
5
6
4 4 1
1 2 3 4
1 4
1 2
2 3
3 3

样例输出

1
2
3
4
yes
no
yes
no

提示

显然整个数列中只有 2, 3 的异或为 1。

对于 20% 的评测用例,1 ≤ n, m ≤ 100;

对于 40% 的评测用例,1 ≤ n, m ≤ 1000;

对于所有评测用例,1 ≤ n, m ≤ 100000 ,0 ≤ x < 220 ,1 ≤ li ≤ ri ≤ n , 0 ≤ Ai < 220。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 2e5 + 10;
int n, m, x;
int l, r;
int a[N];
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> m >> x;
for (int i = 0; i < n; i++)//存数
{
cin >> a[i];
}
while (m--)
{
cin >> l >> r;
bool flag = false;
for (int i = l - 1; i <= r - 1; i++)//注意l,r都要减一,数组下标形式
{
for (int j = i + 1; j <= r - 1; j++)
{
if ((a[i] ^ a[j]) == x)
{
flag = true;
break;
}
}
if (flag) break;//跳出2层for循环的方法
}
if (flag) {
cout << "yes" << endl;
}
else {
cout << "no" << endl;
}
}

return 0;//必须要写
}

MyAns

依旧是暴力枚举,可以得73分。

本题又学习到了如何跳出多层内部循环


2022年第十三届蓝桥杯大赛软件类省赛C/C++大学B组真题

题目 2656: 刷题统计(暴力)

题目描述

小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目,周六和周日每天做 b 道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n 题?

输入格式

输入一行包含三个整数 a, b 和 n.

输出格式

输出一个整数代表天数。

样例输入

1
10 20 99

样例输出

1
8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

int a, b, n;
int sum = 0, day = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> a >> b >> n;
while (1)//循环,7天为一个周期
{
for (int i = 1; i <= 7; i++)
{
if (i == 6 || i == 7)
sum += b;
else
sum += a;

day++;
if (sum >= n)
{
cout << day;
return 0;
}
}
}

return 0;//必须要写
}

MyAns

该解就是简单的暴力枚举,可以过百分之八十的数据。


题目 2657: 修剪灌木(模拟)

题目描述

爱丽丝要完成一项修剪灌木的工作。有 N 棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晚会修剪一棵灌木,让灌木的高度变为 0 厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始,每天向右修剪一棵灌木。当修剪了最右侧的灌木后,她会调转方向,下一天开始向左修剪灌木。直到修剪了最左的灌木后再次调转方向。然后如此循环往复。灌木每天从早上到傍晚会长高 1 厘米,而其余时间不会长高。在第一天的早晨,所有灌木的高度都是 0 厘米。爱丽丝想知道每棵灌木最高长到多高。

输入格式

一个正整数 N ,含义如题面所述。

输出格式

输出 N 行,每行一个整数,第i行表示从左到右第 i 棵树最高能长到多高。

样例输入

1
3

样例输出

1
2
3
4
2
4

提示

对于 30% 的数据,N ≤ 10. 对于 100% 的数据,1 < N ≤ 10000.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 10010;
int a[N];//存放每棵树的实时高度
int n;
int st[N];//存放每颗树的最大高度
int k = 2;

void One_cm()
{
for (int i = 0; i < n; i++)//每棵树每天长1厘米
{
a[i]++;
}
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
while (k)//循环2次即可
{
for (int i = 0; i < n - 1; i++)//从左向右,a[0]到a[n-2]
{
One_cm();
if (a[i] > st[i])
st[i] = a[i];
a[i] = 0;
}
for (int i = n - 1; i >= 1; i--)//从右向左,a[n-1]到a[1]
{
One_cm();
if (a[i] > st[i])
st[i] = a[i];
a[i] = 0;
}
k--;
}

for (int i = 0; i < n; i++)//输出
{
cout << st[i] << endl;
}

return 0;//必须要写
}

MyAns

该题就是简单的模拟,测试样例,然后发现规律,来回两次即可得到每颗树的最大高度。100分


题目 2660: 积木画(动态规划)

题目描述

小明最近迷上了积木画,有这么两种类型的积木,分别为 I 型(大小为 2 个单位面积)和 L 型(大小为 3 个单位面积):

蓝桥杯2022年第十三届省赛真题积木画1

同时,小明有一块面积大小为 2 × N 的画布,画布由 2 × N 个 1 × 1 区域构成。小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式? 积木可以任意旋转,且画布的方向固定。

输入格式

输入一个整数 N,表示画布大小。

输出格式

输出一个整数表示答案。由于答案可能很大,所以输出其对 1000000007 取模后的值。

样例输入

1
3

样例输出

1
5

提示

五种情况如下图所示,颜色只是为了标识不同的积木:

蓝桥杯2022年第十三届省赛真题积木画2

对于所有测试用例,1 ≤ N ≤ 10000000.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e7 + 10;
int n, MOD = 1e9 + 7;
int dp[N];//定义为n列时,不同积木摆放方式的个数

signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
dp[1] = 1;
dp[2] = 2;
dp[3] = 5;
for (int i = 4; i <= n; i++)
{
dp[i] = 2 * dp[i - 1] + dp[i - 3];
dp[i] %= MOD;//在循环内取余,避免数据溢出
}
cout << dp[n];
}

该解用到了动态规划,挺抽象的,直接从dp[n]开始向前推导。

这个博客讲解的还不错:

https://blog.csdn.net/qq_39391544/article/details/124060739?fromshare=blogdetail&sharetype=blogdetail&sharerId=124060739&sharerefer=PC&sharesource=Xing1796_&sharefrom=from_link


2022年第十三届蓝桥杯大赛软件类省赛C/C++大学C组真题

题目 2680: 纸张尺寸(简单暴力)

题目描述

在 ISO 国际标准中定义了 A0 纸张的大小为 1189mm × 841mm,将 A0 纸沿长边对折后为 A1 纸,大小为 841mm × 594mm,在对折的过程中长度直接取下整(实际裁剪时可能有损耗)。将 A1 纸沿长边对折后为 A2 纸,依此类推。

输入纸张的名称,请输出纸张的大小。

输入格式

输入一行包含一个字符串表示纸张的名称,该名称一定是 A0、A1、A2、A3、A4、A5、A6、A7、A8、A9 之一。

输出格式

输出两行,每行包含一个整数,依次表示长边和短边的长度。

样例输入

1
A0

样例输出

1
2
1189
841
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

int a[15], b[15];
string s;
int t;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

a[0] = 1189;
b[0] = 841;
for (int i = 1; i <= 9; i++)//预处理
{
a[i] = b[i - 1];
b[i] = a[i - 1] / 2;
}
cin >> s;
t = s[1] - '0';//字符转化为对应的整数
cout << a[t] << endl << b[t] << endl;
return 0;//必须要写
}

MyAns

很简单的一道题,100分。


题目 2684: 数位排序(sort自定义排序)

题目描述

小蓝对一个数的数位之和很感兴趣,今天他要按照数位之和给数排序。当两个数各个数位之和不同时,将数位和较小的排在前面,当数位之和相等时,将数值小的排在前面。

例如,2022 排在 409 前面,因为 2022 的数位之和是 6,小于 409 的数位之和 13。

又如,6 排在 2022 前面,因为它们的数位之和相同,而 6 小于 2022。

给定正整数 n,m,请问对 1 到 n 采用这种方法排序时,排在第 m 个的元素是多少?

输入格式

输入第一行包含一个正整数 n。

第二行包含一个正整数 m。

输出格式

输出一行包含一个整数,表示答案。

样例输入

1
2
13
5

样例输出

1
3

提示

1 到 13 的排序为:1, 10, 2, 11, 3, 12, 4, 13, 5, 6, 7, 8, 9。第 5 个数为 3。

对于 30% 的评测用例,1 ≤ m ≤ n ≤ 300。

对于 50% 的评测用例,1 ≤ m ≤ n ≤ 1000。

对于所有评测用例,1 ≤ m ≤ n ≤ 106。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e6 + 10;
int a[N];
int n, m;

bool cmp(int x, int y) {//自定义排序规则
int sum1 = 0, sum2 = 0;
int a = x, b = y;
while (a != 0) {
sum1 += a % 10;//x的数位和
a = a / 10;
}
while (b != 0) {
sum2 += b % 10;//y的数位和
b = b / 10;
}
if (sum1 == sum2) {//数位和相同时,值小的在前面
return x < y;
}
return sum1 < sum2;//否则,数位和小的在前面
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> m;
for (int i = 1; i <= n; i++)//存放1到n
{
a[i] = i;
}
sort(a + 1, a + n + 1 , cmp);//sort排序

cout << a[m];
return 0;//必须要写
}

该题要掌握sort函数的自定义排序规则,其次就是如何拆位。


题目 2690: 重新排序(贪心+一维差分)

题目描述

给定一个数组 A 和一些查询 Li , Ri,求数组中第 Li 至第 Ri 个元素之和。

小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能地大。小蓝想知道相比原数组,所有查询结果的总和最多可以增加多少?

输入格式

输入第一行包含一个整数 n。

第二行包含 n 个整数 A1, A2, · · · , An,相邻两个整数之间用一个空格分隔。

第三行包含一个整数 m 表示查询的数目。

接下来 m 行,每行包含两个整数 Li、Ri ,相邻两个整数之间用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。

样例输入

1
2
3
4
5
5
1 2 3 4 5
2
1 3
2 5

样例输出

1
4

提示

原来的和为 6 + 14 = 20,重新排列为 (1, 4, 5, 2, 3) 后和为 10 + 14 = 24,增加了 4。

对于 30% 的评测用例,n, m ≤ 50 ;

对于 50% 的评测用例,n, m ≤ 500 ;

对于 70% 的评测用例,n, m ≤ 5000 ;

对于所有评测用例,1 ≤ n, m ≤ 105,1 ≤ Ai ≤ 106,1 ≤ Li ≤ Ri ≤ 106 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 + 10;
int n, m, l, r;
int a[N], cnt[N], diff[N];
int sum1 = 0, sum2 = 0;
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
for (int i = 1; i <= n; i++)//输入原数组,令下标从1开始
{
cin >> a[i];
}
cin >> m;
for (int i = 0; i < m; i++)//构建差分数组,用于cnt数组的构建
{
cin >> l >> r;
diff[l]++;
diff[r + 1]--;
}
cnt[1] = diff[1];
for (int i = 2; i <= n; i++)//统计每个位置出现的次数
{
cnt[i] = cnt[i - 1] + diff[i];
}

for (int i = 1; i <= n; i++)//计算原数组的区间和
{
sum1 += a[i] * cnt[i];
}
//从小到大排序,确保大数的出现次数更多
sort(a + 1, a + n + 1);
sort(cnt + 1, cnt + n + 1);
for (int i = 1; i <= n; i++)//计算原数组的区间和
{
sum2 += a[i] * cnt[i];
}
cout << sum2 - sum1;
return 0;//必须要写
}

看到题目以为要考前缀和,最后果然还是上了个难度。

利用一维差分统计数列每个位置的出现次数,然后对原数组和cnt数组进行从小到大的排序,

确保大数的出现次数更多,小数的出现次数更少,故大数的“贡献”更多。

做完之后才发现这其实根本上是贪心法


题目 2688: 技能升级(暴力+贪心)

题目描述

小蓝最近正在玩一款 RPG 游戏。他的角色一共有 N 个可以加攻击力的技能。其中第 i 个技能首次升级可以提升 Ai 点攻击力,以后每次升级增加的点数都会减少 Bi。蓝桥杯2022年第十三届省赛真题技能升级(上取整) 次之后,再升级该技能将不会改变攻击力。

现在小蓝可以总计升级 M 次技能,他可以任意选择升级的技能和次数。请你计算小蓝最多可以提高多少点攻击力?

输入格式

输入第一行包含两个整数 N 和 M。

以下 N 行每行包含两个整数 Ai 和 Bi。

输出格式

输出一行包含一个整数表示答案。

样例输入

1
2
3
4
3 6
10 5
9 2
8 1

样例输出

1
47

提示

对于 40% 的评测用例,1 ≤ N, M ≤ 1000;

对于 60% 的评测用例,1 ≤ N ≤ 104 , 1 ≤ M ≤ 107;

对于所有评测用例,1 ≤ N ≤ 105,1 ≤ M ≤ 2 × 109,1 ≤ Ai , Bi ≤ 106。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 + 10;
int n, m, a, b;
vector<int> st;
int sum = 0;
bool cmp(int a, int b)
{
return a > b; // 如果 a > b,则 a 排在前面
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n >> m;
for (int i = 0; i < n; i++)
{
cin >> a >> b;
int t = (a + b - 1) / b;//最大升级次数,上取整
while (t--)
{
st.push_back(a);//存放每个技能升级后可加的攻击力
a -= b;
}
}
sort(st.begin(), st.end(), cmp);//从大到小排序
for (int i = 0; i < m; i++)
{
sum += st[i];//求前m个攻击力的累加之和
}
cout << sum;
return 0;//必须要写
}

一开始想用贪心法,但是不知道如何能够每次取到最大攻击力。

其实只需要把每组样例中可以升级的攻击力都存到vector数组中,最后进行一个降序就完成了。

然而,这依然属于暴力枚举,只能拿39分。


题目 2691: 重复的数(暴力)

题目描述

给定一个数列 A = (a1, a2, · · · , an),给出若干询问,每次询问某个区间 [li ,ri ] 内恰好出现 ki 次的数有多少个。

输入格式

输入第一行包含一个整数 n 表示数列长度。

第二行包含 n 个整数 a1, a2, · · · , an,表示数列中的数。

第三行包含一个整数 m 表示询问次数。

接下来 m 行描述询问,其中第 i 行包含三个整数 li ,ri , ki 表示询问 [li ,ri ] 区间内有多少数出现了 ki 次。

输出格式

输出 m 行,分别对应每个询问的答案。

样例输入

1
2
3
4
5
6
7
8
3
1 2 2
5
1 1 1
1 1 2
1 2 1
1 2 2
1 3 2

样例输出

1
2
3
4
5
1
0
2
0
1

提示

对于 20% 的评测用例,n, m ≤ 500, 1 ≤ a1, a2, · · · , an ≤ 1000;

对于 40% 的评测用例,n, m ≤ 5000;

对于所有评测用例,1 ≤ n, m ≤ 100000, 1 ≤ a1, a2, · · · , an ≤ 100000, 1 ≤ li ≤ ri ≤ n, 1 ≤ ki ≤ n。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define endl '\n'
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f

const int N = 1e5 + 10;
int n, m, l, r, k;
//a数组用于存放数列,st数组用于存放每个数出现的次数,flag数组用于避免重复计算
int a[N],st[N],flag[N];
int sum = 0;
bool cmp(int a, int b)
{
return a > b; // 如果 a > b,则 a 排在前面
}
signed main()
{
ios::sync_with_stdio(0);//关流
cin.tie(0);
cout.tie(0);

cin >> n;
for (int i = 1; i <= n; i++)//存放数列
{
cin >> a[i];
}
cin >> m;
for (int i = 1; i <= m; i++)
{
cin >> l >> r >> k;
for (int i = l; i <= r; i++)//存放该区间内每个数字出现的次数
{
st[a[i]]++;
}
for (int i = l; i <= r; i++)
{
if (st[a[i]] == k)
{
if (flag[a[i]] == 0)//避免重复计算,只统计第一次出现的数
{
sum++;
flag[a[i]] = 1;
}
}
}
cout << sum << endl;
//重置
sum = 0;
memset(flag, 0, sizeof(flag));
memset(st, 0, sizeof(st));
}
return 0;//必须要写
}

MyAns

该题是C组的压轴,没想到直接用暴力做也能拿77分。


-------------本文结束-------------