2022年蓝桥杯C++B组(超详解)

A 九进制转十进制

问题描述:

九进制正整数 ( 2022 ) 9 (2022)_9 (2022)9转换成十进制等于多少?

思路:

请类比二进制转换成十进制:

( 11010 ) 2 = 1 × 2 4 + 1 × 2 3 + 0 × 2 2 + 1 × 2 1 + 0 × 2 0 = ( 26 ) 10 (11010)_2 = 1×2^4 + 1×2^3 + 0×2^2 + 1×2^1 + 0×2^0 = (26)_{10} (11010)2=1×24+1×23+0×22+1×21+0×20=(26)10

C++代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

int main()
{
    int x = 2022;
    int ans = 0, cnt = 0;
    while (x) 
    {
        ans += (x % 10) * pow(9, cnt);
        cnt ++ ;
        x /= 10;
    }
    cout << ans << endl;
}

输出:

1478

B 顺子日期

题目描述:

小明特别喜欢顺子。顺子指的就是连续的三个数字:123456等。顺子日期指的就是在日期的yyyymmdd表示法中,存在任意连续的三位数是一个顺子的日期。例如20220123就是一个顺子日期,因为它出现了一个顺子:123;而20221023则不是一个顺子日期,它一个顺子也没有。小明想知道在整个2022年份中,一共有多少个顺子日期。

思路:

直接硬模拟打暴力即可!

C++代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>

using namespace std;

unordered_map<int, int> um = {{1, 31}, {2, 28}, {3, 31}, {4, 30}, {5, 31},
    {6, 30}, {7, 31}, {8, 31}, {9, 30}, {10, 31}, {11, 30}, {12, 31}
};

string check(int x)
{
    if (x < 10)
        return '0' + to_string(x);
    return to_string(x);
}

int main()
{
    string year = "2022";
    int month = 1;
    int day = 1;
    int ans = 0;
    
    while (month <= 12)
    {
        string t = year + check(month) + check(day);
        int n = t.length();
        for (int i = 0; i < n - 2; i ++ )
        {
            int x = t[i] - '0';
            int y = t[i + 1] - '0';
            int z = t[i + 2] - '0';
            if (y == x + 1 && z == y + 1)
            {
                ans ++ ;
                cout << t << endl;
                break;
            }
        }
        day ++ ;
        if (day > um[month])
            day = 1, month ++ ;
    }
    cout << ans << endl;
}

输出:

20220120
20220121
20220122
20220123
20220124
20220125
20220126
20220127
20220128
20220129
20221012
20221123
20221230
20221231
14

C 刷题统计

问题描述:

小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a a a 道题目,周六和周日每天做 b b b 道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n n n 题?

输入格式:

输入一行包含三个整数 a , b a, b a,b n n n

输出格式:

输出一个整数代表天数。

输入样例:

10 20 99

输出样例:

8

提示:

对于 50 50% 50 的评测用例, 1 ≤ a , b , n ≤ 1 0 6 1 ≤ a, b, n ≤ 10^6 1a,b,n106 . 对于 100 100% 100 的评测用例, 1 ≤ a , b , n ≤ 1 0 18 1 ≤ a, b, n ≤ 10^{18} 1a,b,n1018

思路:

每个星期的刷题量都是 5 × a + 2 × b 5 \times a + 2 \times b 5×a+2×b,我们可以先求出需要多少星期,最后余下来的直接模拟即可!时间复杂度: O ( 1 ) O(1) O(1)

C++代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long LL;

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

    LL cnt = n / (5 * a + 2 * b); // 获取有几周
    LL p = n - cnt * (a * 5 + 2 * b); // 获取剩下的题目数
    
    int ans = 0;
    
    for (int i = 1; i <= 7; i ++ ) 
    {
        if (p <= 0) 
            break;
        if (i <= 5)
            p -= a, ans ++ ;
        else 
            p -= b, ans ++ ;
    }

    cout << ans + cnt * 7;
}

D 修剪灌木

题目描述:

爱丽丝要完成一项修剪灌木的工作。有 N N N 棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晚会修剪一棵灌木,让灌木的高度变为 0 0 0 厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始,每天向右修剪一棵灌木。当修剪了最右侧的灌木后,她会调转方向,下一天开始向左修剪灌木。直到修剪了最左的灌木后再次调转方向。然后如此循环往复。灌木每天从早上到傍晚会长高 1 1 1 厘米,而其余时间不会长高。在第一天的早晨,所有灌木的高度都是 0 0 0 厘米。爱丽丝想知道每棵灌木最高长到多高。

输入格式:

一个正整数 N N N ,含义如题面所述。

输出格式:

输出 N N N 行,每行一个整数,第i行表示从左到右第 i i i 棵树最高能长到多高。

样例输入:

3

样例输出:

4
2
4

提示:

对于 30 30% 30 的数据, N ≤ 10 N ≤ 10 N10。 对于 100 100% 100 的数据, 1 < N ≤ 10000 1 < N ≤ 10000 1<N10000

思路:

模拟一下示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RJx3lt8t-1680352873062)(修建灌木.png)]

对于一颗灌木来说,当爱丽丝从该灌木到离该灌木最远的终点的一个来回是,是该灌木能够长的最高的时候,即对于一个 1 ∼ n 1\sim n 1n 的灌木来说,第 i i i 个灌木最高高度为 2 × m a x ( n − i , i − 1 ) 2 \times max(n - i, i - 1) 2×max(ni,i1)

C++代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int main()
{
    int n;
    cin >> n;
    
    for (int i = 1; i <= n; i ++ )
        cout << 2 * max(n - i, i - 1) << endl;
}

E X进制减法

题目描述:

进制规定了数字在数位上逢几进一。

X X X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!例如说某种 X X X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则 X X X 进制数 321 转换为十进制数为 65

现在有两个 X X X 进制表示的整数 A A A B B B,但是其具体每一数位的进制还不确定,只知道 A A A B B B 是同一进制规则,且每一数位最高为 N N N 进制,最低为二进制。请你算出 A − B A-B AB 的结果最小可能是多少。

请注意,你需要保证 A A A B B B X X X 进制下都是合法的, 即每一数位上的数字要小于其进制。

输入格式:

第一行一个正整数 N N N,含义如题面所述。

第二行一个正整数 M a M_{a} Ma,表示 X X X 进制数 A A A 的位数。

第三行 M a M_{a} Ma 个用空格分开的整数,表示 X X X 进制数 A A A 按从高位到低位顺序各个数位上的数字在十进制下的表示。

第四行一个正整数 M b M_{b} Mb,表示 X X X 进制数 B B B 的位数。

第五行 M b M_{b} Mb 个用空格分开的整数,表示 X X X 进制数 B B B 按从高位到低位顺序各个数位上的数字在十进制下的表示。

请注意,输入中的所有数字都是十进制的。

输出格式:

输出一行一个整数,表示 X X X 进制数 A − B A-B AB 的结果的最小可能值转换为十进制后再模 1000000007 1000000007 1000000007(即 1 0 9 + 7 10^9+7 109+7)的结果。

样例输入:

11
3
10 4 0
3
1 2 0

样例输出:

94

提示:

【样例说明】

当进制为:最低位 2 2 2 进制, 第二数位 5 5 5 进制, 第三数位 11 11 11 进制时, 减法得到的差最小。此时 A A A 在十进制下是 108 108 108 B B B 在十进制下是 14 14 14,差值是 94 94 94

【评测用例规模与约定】

对于 30 % 30 \% 30% 的数据, N ≤ 10 , M a , M b ≤ 8 N \leq 10,M_{a}, M_{b} \leq 8 N10,Ma,Mb8.

对于 100 % 100 \% 100% 的数据, 2 ≤ N ≤ 1000 , 1 ≤ M a , M b ≤ 1 0 5 , A ≥ B 2 \leq N \leq 1000,1 \leq M_{a}, M_{b} \leq 10^5,A \geq B 2N1000,1Ma,Mb105,AB

思路:

对于题目中给的数 321 321 321 来说, 3 3 3 所在的位是 $8 $进制, 2 2 2 所在的位是 10 10 10 进制, 1 1 1 所在的位是 2 2 2 进制。

我们可以发现:

3 × 10 × 2 + 2 × 1 + 1 = 65 3 \times 10 \times 2+2 \times 1+1=65 3×10×2+2×1+1=65

因此,我们可以得到:第 i i i 位的权值 == 比第 i i i 位低的位上的进制之积,即 w i = ∏ j = 0 i a j w_i=\prod_{j=0}^ia_j wi=j=0iaj

a i , b i a_i,b_i ai,bi A , B A,B A,B 的第 i i i 位,那么 A − B A - B AB 的第 i i i w a i × a i − w b i × b i w_{a_i} \times a_i - w_{b_i} \times b_i wai×aiwbi×bi ,由于 A A A B B B 是同一进制规则,可以知道 w a i = w b i = C w_{a_i}=w_{b_i}=C wai=wbi=C,即 A − B A - B AB 的第 i i i C ( a i − b i ) C(a_i-b_i) C(aibi),若想使得 C ( a i − b i ) C(a_i-b_i) C(aibi) 最小, a i − b i a_i-b_i aibi 是固定的,那么可以使得 C C C 最小即可! C = ∏ j = 0 i a j C=\prod_{j=0}^ia_j C=j=0iaj,可以知道我们要使得每一位的进制是最小的,即 m a x ( a i , b i ) + 1 max(a_i, b_i) + 1 max(ai,bi)+1

C++代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int mod = 1e9 + 7;
const int N = 1e5 + 10;

int a[N], b[N], c[N];

int main()
{
    int p;
    cin >> p;
    int ma, mb;
    cin >> ma;
    for (int i = ma; i >= 1; i -- ) 
        cin >> a[i];
    cin >> mb;
    for (int i = mb; i >= 1; i -- ) 
        cin >> b[i];
    
    int n = max(ma, mb);
    for (int i = 1; i <= n; i ++ ) 
            c[i] = max(max(a[i], b[i]) + 1, 2);
    
    LL t = 1;
    int ans1 = 0, ans2 = 0;
    for (int i = 1; i <= n; i ++ ) 
    {
        ans1 = (ans1 + t * a[i]) % mod;
        ans2 = (ans2 + t * b[i]) % mod;
        t = (t * c[i]) % mod;
    }
    
    cout << (ans1 - ans2 + mod) % mod << endl;
}

F 统计子矩阵

题目描述:

给定一个 N × M N \times M N×M 的矩阵 A A A,请你统计有多少个子矩阵 (最小 1 × 1 1 \times 1 1×1, 最大 N × M ) N \times M) N×M) 满足子矩阵中所有数的和不超过给定的整数 K K K

输入格式:

第一行包含三个整数 N , M N, M N,M K K K

之后 N N N 行每行包含 M M M 个整数, 代表矩阵 A A A

输出格式:

一个整数代表答案。

样例输入:

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

样例输出:

19

提示:

【样例说明】

满足条件的子矩阵一共有 19 19 19,包含:

大小为 1 × 1 1 \times 1 1×1 的有 10 10 10 个。

大小为 1 × 2 1 \times 2 1×2 的有 3 3 3 个。 大小为 1 × 3 1 \times 3 1×3 的有 2 2 2 个。

大小为 1 × 4 1 \times 4 1×4 的有 1 1 1 个。

大小为 2 × 1 2 \times 1 2×1 的有 3 3 3 个。

【评测用例规模与约定】

对于 30 % 30 \% 30% 的数据, N , M ≤ 20 N, M \leq 20 N,M20.

对于 70 % 70 \% 70% 的数据, N , M ≤ 100 N, M \leq 100 N,M100.

对于 100 % 100 \% 100% 的数据, 1 ≤ N , M ≤ 500 , 0 ≤ A i j ≤ 1000 , 1 ≤ K ≤ 2.5 × 1 0 8 1 \leq N, M \leq 500,0 \leq A_{i j} \leq 1000,1 \leq K \leq 2.5\times10^8 1N,M500,0Aij1000,1K2.5×108.

思路:

参考

我们可以假想有水平的两条横线,切出了中间一块区域。用 O ( n 2 ) O(n^2) O(n2) 的时间,枚举这两条横线(如下图所示)。

在这里插入图片描述

枚举出两条横线之后,就可以把中间的每一列数给当成一个整体,例如上图 l 1 = 1 , l 2 = 2 l1=1,l2=2 l1=1,l2=2 的情况下,就可以把矩阵视为一个序列 [ 6 , 8 , 10 , 12 ] [6,8,10,12] [6,8,10,12]

我们只需要求出在这个序列中,有几个子序列的元素之和小于等于 k k k 即可。这就把二维问题转化为了一维问题。

那么现在我们还有 O ( m ) O(m) O(m) 的时间来处理子序列的问题。考虑有两个指针 l l l r r r,代表子序列的左端点与右端点。注意到矩阵中所有的元素都是正数,那么显然有下面两个结论:

  1. sum ⁡ ( l , r ) ≤ k \operatorname{sum}(l, r) \leq k sum(l,r)k ,且 l + 1 ≤ r l+1 \leq r l+1r ,则 sum ⁡ ( l + 1 , r ) ≤ k \operatorname{sum}(l+1, r) \leq k sum(l+1,r)k
  2. sum ⁡ ( l , r ) > k \operatorname{sum}(l, r)>k sum(l,r)>k ,且 r + 1 ≤ n r+1 \leq n r+1n ,则 sum ⁡ ( l , r + 1 ) > k \operatorname{sum}(l, r+1)>k sum(l,r+1)>k

在遍历 r r r 时,如果当前的子序列元素和小于等于 k k k ,那么就有 r − l + 1 r-l+1 rl+1 个新答案。如果大于 k k k了,就考虑向右移动 , l l l使得最终的子序列元素和仍然小于等于 k k k

为什么有 r − l + 1 r-l+1 rl+1个新答案?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fEh1nPBa-1680352873066)(统计子矩阵.png)]

右指针从 r − 1 r-1 r1 移动到 r r r 处,那么新增的矩阵数为: [ r ] , [ r − 1 , r ] , [ r − 2 , r ] , [ r − 3 , r ] . . . [ l , r ] [r],[r-1,r],[r-2,r],[r-3,r]...[l,r] [r],[r1,r],[r2,r],[r3,r]...[l,r],共计 r − l + 1 r - l+1 rl+1 条。

举个例子:有一个序列 [ 1 , 3 , 4 , 3 ] [1,3,4,3] [1,3,4,3] ,试求出其中有多少个子序列,满足该子序列的所有元素之和小于等于 10 10 10

  • l = 1 , r = 1 l=1, r=1 l=1,r=1, sum = 1 =1 =1, a n s = 0 + ( 1 − 1 + 1 ) = 1 ans=0+(1-1+1)=1 ans=0+(11+1)=1
  • l = 1 , r = 2 l=1, r=2 l=1,r=2, s u m = 4 sum=4 sum=4, a n s = 1 + ( 2 − 1 + 1 ) = 3 ans=1+(2-1+1)=3 ans=1+(21+1)=3

批注:因为 l = 1 , r = 2 l=1,r=2 l=1,r=2 都可以,那么 l = 2 , r = 2 l=2,r=2 l=2,r=2 肯定可以。

  • l = 1 , r = 3 l=1, r=3 l=1,r=3, s u m = 8 sum=8 sum=8, a n s = 3 + ( 3 − 1 + 1 ) = 6 ans=3+(3-1+1)=6 ans=3+(31+1)=6
  • l = 1 , r = 4 l=1, r=4 l=1,r=4, s u m = 11 sum=11 sum=11

批注:此时考虑移动 l l l 使得 s u m ≤ k sum \leq k sumk。 只需要向右移动一格 l l l 就行了。

  • l = 2 , r = 4 , s u m = 8 , a n s = 6 + ( 4 − 2 + 1 ) = 9 l=2,r=4,sum=8,ans=6+(4-2+1)=9 l=2,r=4,sum=8,ans=6+(42+1)=9

此时 r = 4 r=4 r=4,且有解了,停止循环。

C++代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 510;

LL g[N][N];
LL a[N][N], b[N];
LL n, m, p;

int main()
{
    cin >> n >> m >> p;
    LL ans = 0;
    
    for (int i = 1; i <= n; i ++ ) 
        for (int j = 1; j <= m; j ++ ) 
            cin >> g[i][j];
            
    for (int i = 1; i <= n; i ++ ) 
        for (int j = 1; j <= m; j ++ )
            a[i][j] = a[i - 1][j] + g[i][j];
    
    for (int i = 1; i <= n; i ++ )
        for (int j = i; j <= n; j ++ ) 
        {
            for (int k = 1; k <= m; k ++ )
                b[k] = a[j][k] - a[i - 1][k];
            int l = 1, r = 1, t = 0;
            for (r = 1; r <= m; r ++ ) 
            {
                t += b[r];
                if (t <= p)
                    ans += r - l + 1;
                else 
                {
                    while (t > p)
                    {
                        t -= b[l];
                        l ++ ;
                    }
                    ans += r - l + 1;
                }
            }
        }
    cout << ans << endl;
}

G 积木画

题目描述:

小明最近迷上了积木画,有这么两种类型的积木,分别为 I I I 型(大小为 2 2 2 个单位面积) 和 L L L 型 (大小为 3 3 3 个单位面积):

I 型积木

同时,小明有一块面积大小为 2 × N 2 \times N 2×N 的画布,画布由 2 × N 2 \times N 2×N 1 × 1 1 \times 1 1×1 区域构成。小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式? 积木可以任意旋转,且画布的方向固定。

输入格式:

输入一个整数 N N N,表示画布大小。

输出格式:

输出一个整数表示答案。由于答案可能很大,所以输出其对 1000000007 1000000007 1000000007(即 1 0 9 + 7 10^9+7 109+7)取模后的值。

样例输入:

3

样例输出:

5

提示:

【样例说明】

五种情况如下图所示, 颜色只是为了标识不同的积木:

【评测用例规模与约定】

对于所有测试用例, 1 ≤ N ≤ 1 0 7 1 \leq N \leq 10^7 1N107

思路:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jnufi36G-1680352873070)(积木画DP分析.png)]

  • 第一行比第二行多出一个积木,也就是状态 f[i][0]。此时我们可以用一个 I 型积木在第一行横向填补,这样在填补前第二行就会多出一个积木,也就是 f[i-1][2];也可以用一个 L 型积木填补第 i i i 列和 i − 1 i-1 i1 列,这样填补前两行积木数就相等,也就是 f[i-2][1]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q9Ih4P61-1680352873071)(f[i][0].png)]

  • 两行积木数相等,即状态 f[i][1]。此时有 4 4 4 种情况。我们可以用两个 I 型积木横向填补第 i i i列和第 i − 1 i-1 i1 列,就是 f[i-2][1];可以用一个 I 型积木纵向填补第 i i i 列,也就是 f[i-1][1];可以用一个 L 型积木填补第 i i i 列和第 i − 1 i-1 i1 列的第一行,也就是 f[i-1][2];还可以用一个 L 型积木填补第 i i i 列和第 i − 1 i-1 i1 列的第二行,也就是 f[i-1][0]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rgR5mQHq-1680352873072)(f[i][1].png)]

  • 第二行比第一行多出一个积木,也就是状态 f[i][2]。此时与上一种情况类似,也是两种方案,分别是用一个 I 型积木在第二行横向填补和用一个 L 型积木进行填补。分别是 f[i-1][0]f[i-2][1]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ULkQiJgA-1680352873073)(f[i][3].png)]

C++代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e7 + 10;
const int mod = 1e9 + 7;

int f[N][3];

int main()
{
    int n;
    scanf("%d", &n);
    
    f[0][1] = 1;
    
    for (int i = 1; i <= n; i ++ ) 
    {
        f[i][0] = (f[i - 1][2] + f[i - 2][1]) % mod;
        f[i][1] = ((f[i - 2][1] + f[i - 1][1]) % mod + (f[i - 1][2] + f[i - 1][0]) % mod) % mod;
        f[i][2] = (f[i - 2][1] + f[i - 1][0]) % mod;
    }
    printf("%d\n", f[n][1]);
}

H 扫雷

题目描述:

小明最近迷上了一款名为《扫雷》的游戏。其中有一个关卡的任务如下,在一个二维平面上放置着 n n n 个炸雷,第 i i i 个炸雷 ( x i , y i , r i ) \left(x_{i}, y_{i}, r_{i}\right) (xi,yi,ri) 表示在坐标 ( x i , y i ) \left(x_{i}, y_{i}\right) (xi,yi) 处存在一个炸雷,它的爆炸范围是以半径为 r i r_{i} ri 的一个圆。

为了顺利通过这片土地,需要玩家进行排雷。玩家可以发射 m m m 个排雷火箭,小明已经规划好了每个排雷火箭的发射方向,第 j j j 个排雷火箭 ( x j , y j , r j ) \left(x_{j}, y_{j}, r_{j}\right) (xj,yj,rj) 表示这个排雷火箭将会在 ( x j , y j ) \left(x_{j}, y_{j}\right) (xj,yj) 处爆炸,它的爆炸范围是以半径为 r j r_{j} rj 的一个圆,在其爆炸范围内的炸雷会被引爆。同时,当炸雷被引爆时,在其爆炸范围内的炸雷也会被引爆。现在小明想知道他这次共引爆了几颗炸雷?

你可以把炸雷和排雷火箭都视为平面上的一个点。一个点处可以存在多个炸雷和排雷火箭。当炸雷位于爆炸范围的边界上时也会被引爆。

输入格式:

输入的第一行包含两个整数 n n n m m m

接下来的 n n n 行, 每行三个整数 x i , y i , r i x_{i}, y_{i}, r_{i} xi,yi,ri, 表示一个炸雷的信息。

再接下来的 m m m 行,每行三个整数 x j , y j , r j x_{j}, y_{j}, r_{j} xj,yj,rj, 表示一个排雷火箭的信息。

输出格式:

输出一个整数表示答案。

样例输入:

2 1
2 2 4
4 4 2
0 0 5

样例输出:

2

提示:

【样例说明】

示例图如下, 排雷火箭 1 覆盖了炸雷 1 , 所以炸雷 1 被排除; 炸雷 1 又覆 盖了炸雷 2 , 所以炸雷 2 也被排除。

【评测用例规模与约定】

对于 40 % 40 \% 40% 的评测用例: 0 ≤ x , y ≤ 1 0 9 , 0 ≤ n , m ≤ 1 0 3 , 1 ≤ r ≤ 10 0 \leq x, y \leq 10^{9}, 0 \leq n, m \leq 10^{3}, 1 \leq r \leq 10 0x,y109,0n,m103,1r10.

对于 100 % 100 \% 100% 的评测用例: 0 ≤ x , y ≤ 1 0 9 , 0 ≤ n , m ≤ 5 × 1 0 4 , 1 ≤ r ≤ 10 0 \leq x, y \leq 10^{9}, 0 \leq n, m \leq 5 \times 10^{4}, 1 \leq r \leq 10 0x,y109,0n,m5×104,1r10.

思路:

如果直接枚举每个地雷和所有地雷的情况,时间复杂度为 n 2 n^2 n2,会超时。

由题目可知, r ≤ 10 r \leq 10 r10,那么我们对每个地雷只需要遍历周围一圈 r r r 即可。

由于这样是遍历每个坐标点,而且一共有 5 × 1 0 4 5 \times 10^4 5×104 个地雷,但是它的坐标范围在 [ 0 , 1 0 9 ] [0, 10^9] [0,109],我们需要通过手写散列表来实现离散化的效果。

在这里插入图片描述

那么如何去保证我们求得的哈希值是唯一的呢?

h a s h v a l u e = x × 1 0 9 + y hash_{value} = x \times 10^9 + y hashvalue=x×109+y,因为 0 ≤ x , y ≤ 1 0 9 0≤x,y≤10^9 0x,y109 故这样的运算可以使得高9位是 x x x 的坐标,低9位是 y y y 的坐标,而我们总是保留坐标相同且爆炸半径最大的那个点,所以保证了 x , y x,y x,y 的唯一性,故这样算出来的哈希值一定是唯一的!

C++代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

typedef long long LL;

const int N = 5e4 + 10, M = 1e6 + 7, X = 1e9;

int n, m;
LL h[M];  // hash表
bool st[N];  // 地雷是否已经爆炸了
int id[M], res;  // hash表的下标对应的地雷的信息

struct node {
    int x, y, r;
} a[N];  // 地雷信息

LL get_hash(int x, int y)  // 获取对应的hash值
{
    return(LL)x * X + y;
}

LL find(int x, int y)  // 查询该坐标所对应的hash表中的下标
{
    LL t = get_hash(x, y);  // 获取该点的hash值
    int key = (t % M + M) % M;  // 对该点的hash值进行从1~M位置的映射
    
    while (h[key] != -1 && h[key] != t)  // 如果该下标存储过且并不是原本存储过的哈希值
    {
        key ++ ;  // 开放寻址法,顺次遍历
        if (key == M) key = 0;  // 若已找到遍历完,则返回到开头
    }
    return key;
}

bool check(int x1, int y1, int r, int x, int y)  // 检查一下点x,y是否在圆x1,x2,半径位r的圆内
{
    int d = (x1 - x) * (x1 - x) + (y1 - y) * (y1 - y);
    return d <= r * r;
}

void bfs(int u)
{
    queue<int> q;
    q.push(u);
    st[u] = 1;
    
    while (q.size())
    {
        int t = q.front();
        q.pop();
        
        int x = a[t].x, y = a[t].y, r = a[t].r;
        for (int xx = x - r; xx <= x + r; xx ++ )  // 遍历[x - r, x + r]和[y - r, y + r]的区间内的所有地雷 
            for (int yy = y - r; yy <= y + r; yy ++ )
            {
                int key = find(xx, yy);  // 查询一下该坐标在hash表中的位置
                /*若该点有地雷且并没有爆炸且在上一个地雷的爆炸范围内*/
                if (id[key] && !st[id[key]] && check(x, y, r, xx, yy))
                {
                    int pos = id[key];
                    st[pos] = 1;
                    q.push(pos);
                }
            }
    }
}

int main()
{
    cin >> n >> m;
    memset(h, -1, sizeof h);
    
    int x, y, r;
    for (int i = 1; i <= n; i ++ )  // 把所有地雷读入
    {
        cin >> x >> y >> r;
        a[i] = {x, y, r};
        
        int key = find(x, y);  // 查询一下该坐标在hash表中的位置
        if (h[key] == -1)  // 若该点哈希表中未存值,则直接插入即可
            h[key] = get_hash(x, y);
        
        if (!id[key] || a[id[key]].r < r)  // 若已经存值了,说明坐标相同,那就取最大的半径即可
            id[key] = i;
    }
    
    for (int i = 1; i <= m; i ++ )  // 枚举所有的排雷火箭
    {
        cin >> x >> y >> r;
        for (int xx = x - r; xx <= x + r; xx ++ )  // 查看在区间[x - r, x + r], [y - r, y + r]内的地雷
            for (int yy = y - r; yy <= y + r; yy ++ ) 
            {
                int key = find(xx, yy);
                /*若该地雷存在且没有炸过,且在排雷火箭的范围内*/
                if (id[key] && !st[id[key]] && check(x, y, r, xx, yy))
                    bfs(id[key]);  // 直接让其bfs
            }
    }
    
    for (int i = 1; i <= n; i ++ )  // 查看一下已经爆炸了的地雷
    {
        int key = find(a[i].x, a[i].y);
        int pos = id[key];
        if (pos && st[pos]) res ++ ;
    }
    
    cout << res << endl;
}

I 李白打酒加强版

题目描述:

话说大诗人李白,一生好饮。幸好他从不开车。

一天,他提着酒壶,从家里出来,酒壶中有酒 2 2 2 斗。他边走边唱:

无事街上走,提壶去打酒。
逢店加一倍,遇花喝一斗。

这一路上,他一共遇到店 N N N 次,遇到花 M M M 次。已知最后一次遇到的是花,他正好把酒喝光了。

请你计算李白这一路遇到店和花的顺序,有多少种不同的可能?

注意:壶里没酒( 0 0 0 斗)时遇店是合法的,加倍后还是没酒;但是没酒时遇花是不合法的。

输入格式:

第一行包含两个整数 N N N M M M

输出格式:

输出一个整数表示答案。由于答案可能很大,输出模 1000000007 1000000007 1000000007(即 1 0 9 + 7 10^9+7 109+7)的结果。

样例输入:

5 10

样例输出:

14

提示:

【样例说明】

如果我们用 0 代表遇到花,1 代表遇到店, 14 14 14 种顺序如下:

010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100

【评测用例规模与约定】

对于 40 % 40 \% 40% 的评测用例: 1 ≤ N , M ≤ 10 1 \leq N, M \leq 10 1N,M10

对于 100 % 100 \% 100% 的评测用例: 1 ≤ N , M ≤ 100 1 \leq N, M \leq 100 1N,M100

思路:

参考

采用动态规划的策略:

在这里插入图片描述

初始化操作:当经过 0 0 0 次店, 0 0 0 次花的时候,且李白酒壶里的酒还剩 2 2 2 斗时,即 f [ 0 ] [ 0 ] [ 2 ] = 1 f[0][0][2] = 1 f[0][0][2]=1 ,共有一种情况,当然当经过 0 0 0 次店, 0 0 0 次花的时候,且李白酒壶里的酒不是 2 2 2 斗时,显然时不可能的,即 f [ 0 ] [ 0 ] [ k ] = 0 f[0][0][k]=0 f[0][0][k]=0

我们可以粗略的估算一下 k k k 的范围,因为题目中已知最后一次遇到的是花,他正好把酒喝光了,且逢店加一倍,遇花喝一斗。

可以得出以下式子: 2 × N = M 2 \times N = M 2×N=M,故 k k k 的最大值为 M M M ,体重 1 ≤ M ≤ 100 1 \leq M \leq100 1M100,故 1 ≤ N , M , k ≤ 100 1≤N,M,k≤100 1N,M,k100

最后打印答案的时候不是打印 f [ n ] [ m ] [ 0 ] f[n][m][0] f[n][m][0] ,因为这么打印是无法区分最后是到花还是到店,我们可以往前推一步,如果最后到花,那么喝完的上一步应该是 f [ n − 1 ] [ m ] [ 1 ] f[n - 1][m][1] f[n1][m][1],所以最后答案是 f [ n − 1 ] [ m ] [ 1 ] f[n - 1][m][1] f[n1][m][1]

C++代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110, MOD = 1e9 + 7;

int n, m;
int f[N][N][N];

int main()
{
    cin >> m >> n;
    
    for (int i = 0; i <= n; i ++ ) 
        for (int j = 0; j <= m; j ++ ) 
            for (int k = 0; k < N; k ++ )
            {
                /*经过0次店,0次花的时候,且李白酒壶里的酒2斗时,题目的已知条件*/
                if (i == 0 && j == 0 && k == 2)
                    f[i][j][k] = 1;
                
                /*其余情况下,都为0*/   
                if (i == 0 && j == 0)
                    continue;
                
                if (i)
                    f[i][j][k] = (f[i][j][k] + f[i - 1][j][k + 1]) % MOD;
                
                if (j && k % 2 == 0)
                    f[i][j][k] = (f[i][j][k] + f[i][j - 1][k / 2]) % MOD;
            }
    
    cout << f[n - 1][m][1] << endl;
}

J 砍竹子

题目描述:

这天,小明在砍竹子,他面前有 n n n 棵竹子排成一排,一开始第 i i i 棵竹子的高度为 h i h_{i} hi.

他觉得一棵一棵砍太慢了,决定使用魔法来砍竹子。魔法可以对连续的一段相同高度的竹子使用,假设这一段竹子的高度为 H H H,那么使用一次魔法可以把这一段竹子的高度都变为 ⌊ ⌊ H 2 ⌋ + 1 ⌋ \left\lfloor\sqrt{\left\lfloor\frac{H}{2}\right\rfloor+1}\right\rfloor 2H+1 , 其中 ⌊ x ⌋ \lfloor x\rfloor x 表示对 x x x 向下取整。小明想知道他最少使用多少次魔法可以让所有的竹子的高度都变为 1 1 1

输入格式:

第一行为一个正整数 n n n,表示竹子的棵数。

第二行共 n n n 个空格分开的正整数 h i h_{i} hi,表示每棵竹子的高度。

输出格式:

一个整数表示答案。

样例输入:

6
2 1 4 2 6 7

样例输出

5

提示:

【样例说明】

其中一种方案:

214267 → 214262 → 214222 → 211222 → 111222 → 111111 214267\rightarrow 214262\rightarrow 214222\rightarrow 211222\rightarrow 111222\rightarrow 111111 214267214262214222211222111222111111

共需要 5 步完成

【评测用例规模与约定】

对于 20 % 20 \% 20% 的数据,保证 n ≤ 1000 , h i ≤ 1 0 6 n \leq 1000, h_{i} \leq 10^{6} n1000,hi106

对于 100 % 100 \% 100% 的数据,保证 n ≤ 2 × 1 0 5 , h i ≤ 1 0 18 n \leq 2 \times 10^{5}, h_{i} \leq 10^{18} n2×105,hi1018

思路:

本题是一个最长公共下降子序列的问题,对于任意一个 h h h ,只要它高度降到了与前一个高度下降过程中的公共值,那么它就不需要花费代价继续下降。如果它降得的当前高度与前一个高度没有公共值,则需要多花费一个代价,来降低自己的高度。

如何把公共值计算出来?

s e t set set 来记录上一个位置降到 1 1 1 时的所有中间值,即公共值。

下图是该算法模拟的样例:

在这里插入图片描述

C++代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <set>

typedef long long LL;

using namespace std;

int n;
vector<set<LL>> a;
LL ans;

LL solve(LL x)
{
    return sqrtl(x / 2 + 1);
}

int main()
{
    cin >> n;
    
    a.resize(n + 1);
    
    for (int i = 1; i <= n; i ++ )
    {
        LL x;
        cin >> x;
        
        while (x > 1)
        {
            if (!a[i - 1].count(x)) ans ++ ;
            
            a[i].insert(x);
            
            x = solve(x);
        }
    }
    
    cout << ans << endl;
}

——完结撒花—— \color{Red}{——完结撒花——} ——完结撒花——

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Nie同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值