C++知识点总结(20):疯狂刷题吧Ⅰ

一、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 5n120
对于 100 % 100\% 100% 的数据, 5 ≤ n ≤ 1440 5 \le n \le 1440 5n1440

4. 思路

按照尺取(长跨)的方法,我们使用两个指针 lr,只要是正常脉搏, 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=a1a2+a1a3++a1an+a2a3++an2an1+an2an+an1an

输入格式

输入的第一行包含一个整数 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 1n1000,1ai100
对于 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 1n2×105,1ai1000

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 a1a2+a1a3++a1an
= 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 求出。

根据给出的代码,我们来分析一下思路:

  1. 首先,定义变量 n,表示数组的长度,和变量 ans,表示计算结果。
  2. 定义数组 a[]s[],分别存储输入的数值和前缀和。
  3. 遍历数组 a,将每个元素输入,并计算前缀和 s
  4. 使用循环遍历数组 a 的第二个到倒数第二个元素,然后依次计算 a[i] * (s[n] - s[i]) 的结果并累加到 ans 中。
  5. 输出结果 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 1n105 0 ≤ a i ≤ 2147483647 0\le a_i \le2147483647 0ai2147483647

4. 思路

利用差分,我们列出两个数组:

数组名12345
a[]32478
c[]3-1231

如果一个区间中的每个数字同时 + 1 +1 +1 或者 − 1 -1 1,那么只需要变动 lr+1 的数字,这就是选择差分的目的。

当然,也会出现两种情况:

  • 可能会改变两个数,一边 + 1 +1 +1 一边 − 1 -1 1
  • 也可能只改变一个数,要么 + 1 +1 +1 要么 − 1 -1 1

我们会优先选择该变量个数

举个例子:

数组12345
a[]53728
c[]5-24-56
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[]50000

求最少次数方法:
假如说要进行 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 +11 组合的 3 3 3 − 1 -1 1,所以会多出 3 3 3 种情况。那么,就会有 ∣ x − y ∣ + 1 |x-y|+1 xy+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 mn 个龙头关闭。
现在给出 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 1n10000 1 ≤ m ≤ 100 1\le m\le100 1m100 m ≤ n m\le n mn 1 ≤ w i ≤ 100 1\le w_i\le100 1wi100

4. 思路

  1. 根据输入的数据,将每个水龙头可接水的速度存储在数组 b[] 中。
  2. 对于每个待接的水桶,遍历水龙头的数组 b[],找到当前最小的接水时间对应的水龙头,并将该水龙头的接水时间增加上当前水桶的接水时间。通过这个过程,每个水桶都会被分配到一个最空闲的水龙头上。
  3. 遍历水龙头的数组 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 5n10000

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 0n10 0 ≤ m ≤ 1000 0 \le m \le 1000 0m1000。名字长度小于 100 100 100 字符。

3.2 思路

输入比较复杂,我们分开来分别看一看,到底什么意思。

3
Makle
Bland
Cracl
10
1 1 2 3 3 2 1 2 2 2
  1. 第一行输入的是参加竞选人的数量,我们把它记作 n n n
  2. 接下去 n n n 行,每行有一个参加竞选的人名,我们把它称为 n a m e name name
  3. n + 2 n+2 n+2 行,表示投票数量,我们把它记作 m m m
  4. 接下来一行,有 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;
}
  • 53
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值