一、P1008 三连击
1. 来源
N O I P NOIP NOIP 1998 1998 1998 普及组 T 1 T_1 T1
2. 考察知识
- 暴力枚举
- 桶的思想
3. 审题
题目描述
将 < 1 , 2 , … , 9 > <1,2,…,9> <1,2,…,9> 共 9 9 9 个数分成 3 3 3 组,分别组成 3 3 3 个三位数,且使这 3 3 3 个三位数构成 1 : 2 : 3 1:2:3 1:2:3 的比例,试求出所有满足条件的 3 3 3 个三位数。
输入描述
无
输出描述
若干行,每行3个数字。按照每行第1个数字升序排列。
样例1
输入
无
输出192 384 576 ...
4. 思路
我们找出枚举三要素:
- 对象:理论上只有 1 1 1 个,因为后两个数字可以用第一个数字求出来。
- 范围: 123 123 123 ~ 329 329 329
- 条件:数位分离后,使用桶
cnt[]
进行计数
5. 参考答案
#include <iostream>
using namespace std;
int main()
{
for (int i = 123; i <= 329; i++)
{
// 求得比例,初始化桶
int num1 = i;
int num2 = num1 * 2;
int num3 = num1 * 3;
int cnt[15] = {};
bool flag = true;
// 数位分离
while (num1)
{
cnt[num1 % 10]++;
cnt[num2 % 10]++;
cnt[num3 % 10]++;
num1 /= 10;
num2 /= 10;
num3 /= 10;
}
// 判断是否只出现过一次1~9
for (int j = 1; j <= 9; j++)
{
if (cnt[j] != 1)
{
flag = false;
break;
}
}
// 判断是否满足要求
if (flag)
{
cout << i << " " << i * 2 << " " << i * 3 << endl;
}
}
return 0;
}
二、T1076 最久正常脉搏 改编
1. 来源
根据《信息学奥赛》题目 1076 1076 1076 进行改编。
2. 考察知识
- 文件读写
- 尺取(长跨)
3. 审题
题目描述
在 X X X 国某医院的 I C U ICU ICU 中,张三正紧紧盯着病人的脉搏,以便当脉搏停止时随时进行抢救。而你作为他的助手,需要将机器记录的每分钟实时脉搏统计下来,并进行分析:在 n n n 分钟内,病人最长维持了多长时间的正常脉搏。已知人的正常脉搏是每分钟 60 60 60 ~ 100 100 100 次,大于或小于这个范围,都不是正常脉搏。若病人在这段时间内没有正常脉搏,则输出 0 0 0。
输入描述
输入文件
keep.in
。
共 2 2 2 行:
第一行包含一个数 n n n,代表一共统计了 n n n 分钟;
第二行包含 n n n 个数,代表每分钟病人的脉搏。
输出描述
输出文件
keep.out
。
1 1 1 行,包含 1 1 1 个数,代表病人维持正常脉搏的最长时长。
样例1
输入
10 30 20 50 67 70 75 65 30 50 50
输出
4
提示
对于 30 % 30\% 30% 的数据, 5 ≤ n ≤ 120 5 \le n \le 120 5≤n≤120。
对于 100 % 100\% 100% 的数据, 5 ≤ n ≤ 1440 5 \le n \le 1440 5≤n≤1440。
4. 思路
按照尺取(长跨)的方法,我们使用两个指针 l
和 r
,只要是正常脉搏, r
就进行移动;否则更新结果, l
跨到 r
的地方。
5. 参考答案
#include <iostream>
#include <cstdio>
using namespace std;
int n, maxt;
int l = 1, r = 1;
int a[1445];
int main()
{
freopen("keep.in", "r", stdin);
freopen("keep.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
while (l <= n)
{
if (a[r] >= 60 && a[r] <= 100 && r <= n) // 右指针移动
{
r++;
}
else // 更新结果,左指针移动
{
maxt = max(maxt, r-l);
l = r + 1;
r++;
}
}
cout << maxt;
fclose(stdin);
fclose(stdout);
return 0;
}
三、P8772 特殊数列和
1. 来源
蓝桥杯 2022 2022 2022 省赛 A A A 组 T C T_C TC
2. 考察知识
- 前缀和
- 区间和
3. 审题
题目描述
给定 n n n 个整数 a 1 , a 2 , ⋯ , a n a_{1}, a_{2}, \cdots, a_{n} a1,a2,⋯,an, 求它们两两相乘再相加的和,即
S = a 1 ⋅ a 2 + a 1 ⋅ a 3 + ⋯ + a 1 ⋅ a n + a 2 ⋅ a 3 + ⋯ + a n − 2 ⋅ a n − 1 + a n − 2 ⋅ a n + a n − 1 ⋅ a n S=a_{1} \cdot a_{2}+a_{1} \cdot a_{3}+\cdots+a_{1} \cdot a_{n}+a_{2} \cdot a_{3}+\cdots+a_{n-2} \cdot a_{n-1}+a_{n-2} \cdot a_{n}+a_{n-1} \cdot a_{n} S=a1⋅a2+a1⋅a3+⋯+a1⋅an+a2⋅a3+⋯+an−2⋅an−1+an−2⋅an+an−1⋅an
输入格式
输入的第一行包含一个整数 n n n 。
第二行包含 n n n 个整数 a 1 , a 2 , ⋯ a n a_{1}, a_{2}, \cdots a_{n} a1,a2,⋯an 。
输出格式
输出一个整数 S S S,表示所求的和。请使用合适的数据类型进行运算。
样例1
输入
4 1 3 6 9
输出
117
提示
对于 30 % 30 \% 30% 的数据, 1 ≤ n ≤ 1000 , 1 ≤ a i ≤ 100 1 \leq n \leq 1000,1 \leq a_{i} \leq 100 1≤n≤1000,1≤ai≤100 。
对于 100 % 100 \% 100% 评测用例, 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ a i ≤ 1000 1 \leq n \leq 2\times10^5,1 \leq a_{i} \leq 1000 1≤n≤2×105,1≤ai≤1000 。
4. 思路
首先,我们进行算式转换:
a 1 ⋅ a 2 + a 1 ⋅ a 3 + ⋅ ⋅ ⋅ + a 1 ⋅ a n a_1 \cdot a_2 + a_1 \cdot a_3 + \cdot \cdot \cdot + a_1 \cdot a_n a1⋅a2+a1⋅a3+⋅⋅⋅+a1⋅an
= a 1 ⋅ ( a 2 + a 3 + ⋅ ⋅ ⋅ + a n ) = a_1 \cdot (a_2 + a_3 + \cdot \cdot \cdot + a_n) =a1⋅(a2+a3+⋅⋅⋅+an)
然后,我们思考一下, ( a 2 + a 3 + ⋅ ⋅ ⋅ + a n ) (a_2 + a_3 + \cdot \cdot \cdot + a_n) (a2+a3+⋅⋅⋅+an) 应该如何计算。
其实,我们可以利用前缀和数组 b b b 求出。
根据给出的代码,我们来分析一下思路:
- 首先,定义变量
n
,表示数组的长度,和变量ans
,表示计算结果。 - 定义数组
a[]
、s[]
,分别存储输入的数值和前缀和。 - 遍历数组
a
,将每个元素输入,并计算前缀和s
。 - 使用循环遍历数组
a
的第二个到倒数第二个元素,然后依次计算a[i] * (s[n] - s[i])
的结果并累加到ans
中。 - 输出结果
ans
。
5. 参考答案
#include <iostream>
using namespace std;
long long n, ans;
long long a[100005];
long long s[100005];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
s[i] = s[i-1] + a[i];
}
for (int i = 1; i <= n-1; i++)
{
ans += a[i] * (s[n] - s[i]);
}
cout << ans;
return 0;
}
四、数据统一
1. 来源
无
2. 考察知识
- 差分
3. 审题
题目描述
给定一个长度为 n n n 的数列 < a 1 , a 2 , ⋯ , a n > <a_1,a_2,⋯,a_n> <a1,a2,⋯,an>,每次可以选择一个区间 [ l , r ] [l,r] [l,r],使这个区间内的数都加 1 1 1 或者都减 1 1 1。请问至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列有多少种。
输入描述
共 2 2 2 行:
第 1 1 1 行包含一个整数 n n n。
第 2 2 2行,包含 n n n 个整数,代表 a 1 a_1 a1~ a n a_n an。
输出描述
共 2 2 2 行:
第一行输出最少操作次数。
第二行输出最终能得到多少结果。
样例1
输入
4 1 1 2 2
输出
1 2
提示
对于 100 % 100\% 100% 的数据,
1 ≤ n ≤ 1 0 5 1\le n\le10^5 1≤n≤105, 0 ≤ a i ≤ 2147483647 0\le a_i \le2147483647 0≤ai≤2147483647
4. 思路
利用差分,我们列出两个数组:
数组名 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
a[] | 3 | 2 | 4 | 7 | 8 |
c[] | 3 | -1 | 2 | 3 | 1 |
如果一个区间中的每个数字同时
+
1
+1
+1 或者
−
1
-1
−1,那么只需要变动 l
和 r+1
的数字,这就是选择差分的目的。
当然,也会出现两种情况:
- 可能会改变两个数,一边 + 1 +1 +1 一边 − 1 -1 −1;
- 也可能只改变一个数,要么 + 1 +1 +1 要么 − 1 -1 −1。
我们会优先选择该变量个数
举个例子:
数组 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
a[] | 5 | 3 | 7 | 2 | 8 |
c[] | 5 | -2 | 4 | -5 | 6 |
c[] | +1 | -1 | +1 | -1 | -1 |
c[] | +1 | -1 | +1 | -1 | |
c[] | -1 | +1 | -1 | ||
c[] | -1 | +1 | -1 | ||
c[] | +1 | -1 | |||
c[] | -1 | ||||
结果c[] | 5 | 0 | 0 | 0 | 0 |
求最少次数方法:
假如说要进行
10
10
10 次
−
1
-1
−1,
7
7
7 次
+
1
+1
+1 ,那么我们可以定义两个变量
x
x
x 和
y
y
y, 分别存储正数和以及负数和,那么通过 max()
函数就可以解决。
求相同数的情况数方法:
看差分数组的第一项,就是改变后数组的第
0
0
0 项和第
1
1
1 项的差,就是改变后数组的每个相同的元素。按照表格的例子,多出了
3
3
3 个不可以组成
+
1
−
1
+1-1
+1−1 组合的
3
3
3 个
−
1
-1
−1,所以会多出
3
3
3 种情况。那么,就会有
∣
x
−
y
∣
+
1
|x-y|+1
∣x−y∣+1 种情况。
5. 参考答案
#include <iostream>
#include <cmath>
using namespace std;
long long n, x, y;
int a[100005];
int c[100005];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
c[i] = a[i] - a[i-1];
}
for (int i = 2; i <= n; i++)
{
if (c[i] > 0)
{
x += c[i];
}
else
{
y += abs(c[i]);
}
}
cout << max(x, y) << endl;
cout << abs(x-y) + 1;
return 0;
}
五、回文质数
1. 来源
无
2. 考察知识
- 判断回文数
- 筛质数
3. 审题
求 1 1 1 ~ n n n 之间所有的回文质数。
4. 思路
首先,我们要知道一个数学理论:
偶数位数的回文数一定是 11 11 11 的倍数。
所以,最多遍历到七位数即可,而不是题目告诉我们的 1 0 8 10^8 108。
5. 参考答案
#include <iostream>
using namespace std;
int n;
int isPrime[10000005];
int main()
{
cin >> n;
if (n > 9999999)
{
n = 9999999;
}
for (int i = 2; i <= n; i++)
{
isPrime[i] = 1;
}
for (int i = 2; i * i <= n; i++)
{
if (isPrime[i])
{
for (int j = i * i; j <= n; j++)
{
isPrime[j] = 0;
}
}
}
for (int i = 2; i <= n; i++)
{
if (isPrime[i] == 1)
{
int x = i, newx = 0;
while (x)
{
newx = newx * 10 + x % 10;
x /= 10;
}
if (newx == i)
{
cout << i << endl;
}
}
}
return 0;
}
六、P1190 接水问题
1. 来源
N O I P NOIP NOIP 2010 2010 2010 普及组 T 2 T_2 T2
2. 考察知识
- 贪心算法
3. 审题
题目描述
学校里有一个水房,水房里一共装有m个龙头可供同学们打开水,每个龙头每秒钟的供水量相等,均为 1 1 1。
现在有 n n n 名同学准备接水,他们的初始接水顺序已经确定。将这些同学按接水顺序从 1 1 1 到 n n n 编号, i i i 号同学的接水量为 w i w_i wi。接水开始时, 1 1 1 到 m m m 号同学各占一个水龙头,并同时打开水龙头接水。当其中某名同学j完成其接水量要求 w j w_j wj 后,下一名排队等候接水的同学 k k k 马上接替j同学的位置开始接水。这个换人的过程是瞬间完成的,且没有任何水的浪费。即j同学第 x x x 秒结束时完成接水,则 k k k 同学第 x + 1 x+1 x+1 秒立刻开始接水。若当前接水人数 n n n 不足 m m m ,则只有 n n n 个龙头供水,其它 m − n m-n m−n 个龙头关闭。
现在给出 n n n 名同学的接水量,按照上述接水规则,问所有同学都接完水至少需要多少秒。
输入描述
第一行两个整数 n n n 和 m m m ,用一个空格隔开,分别表示接水人数和龙头个数。
第二行 n n n 个整数 w 1 , w 2 , ⋅ ⋅ ⋅ w n w_1,w_2,\cdot \cdot \cdot w_n w1,w2,⋅⋅⋅wn,每两个整数之间用一个空格隔开, w i w_i wi 表示 i i i 号同学的接水量。
输出描述
输出只有一行, 1 1 1 个整数,表示接水所需的总时间。
样例1
输入
5 3 4 4 1 2 1
输出
4
样例2
输入
8 4 23 71 87 32 70 93 80 76
输出
163
提示
1 ≤ n ≤ 10000 1\le n\le10000 1≤n≤10000, 1 ≤ m ≤ 100 1\le m\le100 1≤m≤100 且 m ≤ n m\le n m≤n, 1 ≤ w i ≤ 100 1\le w_i\le100 1≤wi≤100
4. 思路
- 根据输入的数据,将每个水龙头可接水的速度存储在数组
b[]
中。 - 对于每个待接的水桶,遍历水龙头的数组
b[]
,找到当前最小的接水时间对应的水龙头,并将该水龙头的接水时间增加上当前水桶的接水时间。通过这个过程,每个水桶都会被分配到一个最空闲的水龙头上。 - 遍历水龙头的数组
b[]
,找到其中的最大值,即为完成全部水桶接水所需的最长时间,将其输出。
5. 参考代码
#include <iostream>
using namespace std;
// m对应b[]
int n, m, maxn;
int a[10005];
int b[105];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
// 打擂台
int minn = 1e8, pos;
for (int j = 1; j <= m; j++)
{
if (b[j] < minn) // 遍历的是水龙头
{
minn = b[j];
pos = j;
}
}
b[pos] += a[i];
}
for (int i = 1; i <= m; i++)
{
maxn = max(maxn, b[i]);
}
cout << maxn;
return 0;
}
七、习题
1. 连比数
1.1 审题
题目描述
将 < 1 , 2 , ⋅ ⋅ ⋅ , 9 > <1,2,\cdot \cdot \cdot,9> <1,2,⋅⋅⋅,9> 共 9 9 9 个数分成 3 3 3 组,分别组成 3 3 3 个三位数,输入比值 a a a、 b b b、 c c c,使这 3 3 3 个三位数构成 a : b : c a:b:c a:b:c 的比例,试求出所有满足条件的 3 3 3 个三位数。若不存在该比值,则输出
No!!!
(注意:"No!!!"
一个字符都不能差)
输入描述
一行,包含 3 3 3 个整数, a a a、 b b b、 c c c。代表三个数的比值。
输出描述
若干行,每行 3 3 3 个数字。按照每行第 1 1 1 个数字升序排列。
样例1
输入
1 2 3
输出
192 384 576 219 438 657 273 546 819 327 654 981
提示
保证 a < b < c a<b<c a<b<c
1.2 参考答案
#include <iostream>
using namespace std;
int main()
{
int a, b, c;
bool haveNum = false;
cin >> a >> b >> c;
for (int i = 123; i <= 329; i++)
{
int num1 = i * a;
int num2 = i * b;
int num3 = i * c;
int cnt[15] = {};
bool flag = true;
while (num1)
{
cnt[num1 % 10]++;
cnt[num2 % 10]++;
cnt[num3 % 10]++;
num1 /= 10;
num2 /= 10;
num3 /= 10;
}
for (int i = 1; i <= 9; i++)
{
if (cnt[i] != 1)
{
flag = false;
break;
}
}
if (flag)
{
haveNum = true;
cout << i * a << " " << i * b << " " << i * c << endl;
}
}
if (!haveNum)
{
cout << "No!!!";
}
return 0;
}
2. 最长递减子串
2.1 审题
题目描述
给定一个由 n n n 个正整数组成的数字串 n u m num num,现请你求出 n u m num num 中最长连续递减子串的长度并输出,规定最短子串长度为 2 2 2,若没有则输出 0 0 0。
连续递减:每个元素都一定小于上一个元素。
输入描述
2 2 2 行,第 1 1 1 行包含 1 1 1 个数字 n n n,代表数字的个数 n n n。 第 2 2 2 行包含 n n n 个数字,代表数字串中的每一个数字。
输出描述
1 1 1 行,包含 n u m num num 中最长连续递减字串的长度。
样例1
输入
10 1 2 4 6 5 4 1 2 8 7
输出
4
提示
5 ≤ n ≤ 10000 5 \le n \le10000 5≤n≤10000
1.2 参考答案
#include <iostream>
using namespace std;
int n, l = 1, r = 1, maxn;
int a[10005];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 尺取
while (l <= n)
{
int temp = a[r];
if (a[r] < a[r-1] && r <= n) // 右指针移动
{
r++;
}
else // 更新结果,左指针移动
{
maxn = max(maxn, r-l+1);
l = r + 1;
r++;
}
}
if (maxn == 1) // 全是递增的序列每次r-l+1的+1会影响结果
{
cout << 0;
}
else
{
cout << maxn;
}
return 0;
}
3. 投票大选
3.1 审题
题目描述
M M M 国最近马上要开始国家领导大选啦,关于候选人一共有 n n n 位,需要通过投票决定谁当选最后的国家总统,但是选票过多,人工筛选太过于繁琐,故请你设计一个程序,输入参选人数 n n n,以及其名字,并按输入顺序进行编号。随后输入 m m m,代表有 m m m 张有效选票,接下来输入每个选票对应的参选人编号。最后所得票数最多的最终当选总统。注意:本题中不会出现票数相同的情况。
输入描述
共 n + 3 n+3 n+3 行:
第 1 1 1 行包含一个整数 n n n,代表 n n n 位参选人。
第 2 2 2 ~ n + 1 n+1 n+1 行,每行包含一个字符串,代表候选人的姓名。
第 n + 2 n+2 n+2 行包含一个整数 m m m,代表 m m m 张有效选票。
第 n + 3 n+3 n+3 行包含 m m m 个整数,表示每张选票对应的编号。
输出描述
1 1 1 行,参选编号以及总统姓名,中间用空格隔开。
样例1
输入
3 Makle Bland Cracl 10 1 1 2 3 3 2 1 2 2 2
输出
2 Bland
提示
对于 100 % 100\% 100% 的数据, 0 ≤ n ≤ 10 0 \le n \le 10 0≤n≤10, 0 ≤ m ≤ 1000 0 \le m \le 1000 0≤m≤1000。名字长度小于 100 100 100 字符。
3.2 思路
输入比较复杂,我们分开来分别看一看,到底什么意思。
3
Makle
Bland
Cracl
10
1 1 2 3 3 2 1 2 2 2
- 第一行输入的是参加竞选人的数量,我们把它记作 n n n。
- 接下去 n n n 行,每行有一个参加竞选的人名,我们把它称为 n a m e name name。
- 第 n + 2 n+2 n+2 行,表示投票数量,我们把它记作 m m m。
- 接下来一行,有 m m m 个整数,表示投票给人的编号,我们把它记作 i d id id。
这样,我们就可以利用结构体(struct
)轻松完成了。如果没有学过结构体,也没有关系,你可以利用排序算法和一个桶
n
a
m
e
[
]
name[]
name[] 完成哦。
3.3 参考答案
#include <iostream>
#include <algorithm>
using namespace std;
int n, m, choose;
struct Node
{
int id, num;
char name[105];
}person[15];
bool cmp(Node a, Node b)
{
return a.num > b.num;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> person[i].name;
person[i].id = i;
}
cin >> m;
for (int i = 1; i <= m; i++)
{
cin >> choose;
person[choose].num++;
}
sort(person+1, person+n+1, cmp);
cout << person[1].id << " " << person[1].name;
return 0;
}