第十届蓝桥杯大赛个人赛决赛B组

194 阅读2分钟

A.平方序列

题目

小明想找到两个正整数XXYY,满足2019<X<Y2019<X<Y

201922019^2X2X^2Y2Y^2组成等差数列。

请你求出在所有可能的解中,X+YX+Y的最小值是多少?

思路

由于是填空题,所以可以暴力枚举一个区间段的数字,来判断是否是最小值。

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
int main() {
    ll sum = 2019 * 2019;
    ll MIN = INT_MAX;
    for (ll x = 2020; x < 10000; x++) {
        for (ll y = 2021; y < 10000; y++) {
            if (2 * x * x - y * y == sum) {
                MIN = min(MIN, x + y);
            } 
        }
    }
    cout << MIN << endl;
    return 0;

}

B.质数拆分

题目

20192019拆分为若干个两两不同的质数之和,一共有多少种不同的方法?

注意交换顺序视为同一种方法,例如2+2017=20192+2017=20192017+2=20192017+2=2019视为同一种方法

思路

填空题,这道题由于采用的是两两不同的质数,所以每个质数只能使用一次,所以抽象出来就是经典的01背包问题,直接用动态规划即可解决

具体步骤:

  1. 先计算出20192019的所有质数
  2. 直接套01背包模版

我这里使用的滚动数组来优化了01背包的空间复杂度

状态转移表达式:dp[j]+=dp[jvec[i]];dp\left[j\right]\:+=\:dp\left[j\:-\:vec\left[i\right]\right];

代码

#include<bits/stdc++.h>
using namespace std;
#define mm(a) memset(a, 0, sizeof(a))
const int MAXN = 3000;
typedef long long ll;
ll dp[MAXN];
bool isPrime(int n) {
    if (n <= 3) {
        return n > 1;
    }
    for (int i = 2; i < n; i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

int main() {
    vector<ll> vec;
    for (ll i = 1; i <= 2019; i++) {
        if (isPrime(i)) vec.push_back(i);
    }
    mm(dp);
    dp[0] = 1;
    for (int i = 0; i < vec.size(); i++) {
        for (int j = 2019; j >= vec[i]; j--) {
            dp[j] += dp[j - vec[i]];
        }
    }
    cout << dp[2019] << endl;
    return 0;
}

C.拼接

题目

小明要把一根木头切成两段,然后拼接成一个直角。

如下图所示, 他把中间部分分成了nxnnxn的小正方形, 他标记了每个小正方形属于左边还是右边。然后沿两边的分界线将木头切断,将右边旋转向上后拼接在一起。

image-20210509220118566

要求每个小正方形都正好属于左边或右边,而且同一边的必须是连通的。

在拼接时,拼接的部位必须保持在原来大正方形里面。

请问,对于7×77×7的小正方形,有多少种合法的划分小正方形的方式

思路

代码

D.求值

题目

学习了约数后,小明对于约数很好奇,他发现,给定一个正整数tt,总是可

以找到含有tt个约数的整数。小明对于含有tt个约数的最小数非常感兴趣,并

把它定义为StS_t。例如S1=1S2=2S3=4Sa=6S_1=1,S_2=2,S_3=4,S_a=6,\dots 。

现在小明想知道,当t=100t=100时,S,是多少?即S100S_{_{100}}是多少

思路

填空题,可以直接用暴力来做,直接计算每个数的约数个数 当约数个数为100100的时候,就是结果值

代码

#include<bits/stdc++.h>
using namespace std;
int solve(int n)
{
    int count = 0;
    for (int i = 1; i <= n; ++i)
        if (n % i == 0) count++;
    return count;
}

int main() {
    for (int i = 1; i < 100000; i++) {
        if (solve(i) == 100) {
            cout << i << endl;
            break;
        }
    }

    return 0;
}

E.路径计数

题目

从一个5x55x5的方格矩阵的左上角出发,沿着方格的边走,满足以下条件的

路线有多少种?

  • 总长度不超过1212
  • 最后回到左上角;
  • 路线不自交;
  • 不走出555 * 5的方格矩阵范围之外。

如下图所示, ABCABC是三种合法的路线。注意BBCC由于方向不同, 所以

视为不同的路线

image-20210509220604792

思路

填空题,经典搜索问题,这里我采用的是DFS回溯剪枝来统计路径数。

题目有几个要求:

  1. 总长度不超过1212(用计数来统计路程)
  2. 最后回到左上角(即起始点)
  3. 路线不自交(只能经过某一个点一次,起始点除外)
  4. 不走出方格范围(一直要保证在这个范围之内)

题目给定的是555 * 5的矩阵,但是每次都是经过某一个点,所以每行每列应该都是66 需要的变量:

  1. 定义一个矩阵来存放是否经过某个点
  2. 定义一个变量(ansans)来存放结果,一共有多少种路径
  3. 定义一个二维数组来存放具体的枚举方向

DFSDFS每经过一个点,需要判断其是否在区间内,以及是否访问过该点和路径长度是否超过了1212,进行剪枝; 当长度等于1111,且这个时候位置正好在起始点的右边,或者起始点的下面时,即为正确的路径,需要统计;

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 6;
bool vis[N][N];
int d[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int ans = 0;

bool inArea(int x, int y) {
    return x >= 0 && x < 6 && y >= 0 && y < 6;
}

void dfs(int x, int y, int len) {
    // 如果越界,长度大于12,或者访问过 直接返回
    if (!inArea(x, y) || len > 12 || vis[x][y]) return; 
    
    // 如果长度小于12 且为出发点的右边的点 或者 下面的点 就算可以到达
    // 其中有特例 就是 刚出发就停止了 步数为1的时候 还没有开始遍历
    // 所以需要排除这两个特例
    if (len <= 11 && ((x == 0 && y == 1) || (x == 1 && y == 0))) {
        if (len != 1) {
            ans++;
            return;
        }
    }
    vis[x][y] = true;// 标记改点已经被访问
    for (int i = 0; i < 4; i++) {
        int x1 = x + d[i][0], y1 = y + d[i][1];
        dfs(x1, y1, len + 1);
    }
    vis[x][y] = false;
}
 

int main () {
    dfs(0, 0, 0);
    cout << ans<< endl; // 206
    return 0;
}

F.最优包含

题目

问题描述

我们称一个字符串SS包含字符串TT是指TTSS的一个子序列,即可以从字符串SS中抽出若干个字符,它们按原来的顺序组合成一个新的字符串与TT完全一样。

给定两个字符串STS和T,请问最少修改SS中的多少个字符,能使SS包含TT?

输入格式

输入两行,每行一个字符串。第一行的字符串为S,第二行的字符串为T。

两个字符串均非空而且只包含大写英文字母。

输出格式

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

样例输入

ABCDEABCD
XAABZ

样例输出

3

思路

编程题,有点类似与LCSLCS(找最长公共子序列),思路大体都是一样的,用DPDP

定义状态

dp[i][j]dp[i][j] 表示字符串SS的前ii个字符,变为字符串TTjj个字符需要修改SS中的多少字符

状态初始化

dp[i][1]=dp[i1][0](a[i]!=b[1])dp[i][1] = dp[i - 1][0]\: \: (a[i] != b[1])

dp[i][0]=0(a[i]==b[1])dp[i][0] = 0\: \: (a[i] == b[1])

dp[0][j]=dp[0][j1](a[1]==b[j])dp[0][j] = dp[0][j - 1] \: (a[1] == b[j])

dp[0][1]=dp[0][i1]+1(a[1]!=b[j])dp[0][1] = dp[0][i - 1] + 1 \: \: (a[1] != b[j])

状态转移

当字符串SS的前i1i - 1个,和字符串TT的前jj个匹配之后,字符串SS的第ii个不需要匹配。

dp[i][j]=dp[i1][j]dp[i][j] = dp[i - 1][j]

当字符串SS的前ii个,和字符串TT的前j1j - 1个匹配之后,则T还多了第jj个出来,所以需要将字符串SS后面添加一个T[j]T[j],才能匹配TT;

dp[i][j]=dp[i][j1]+1dp[i][j] = dp[i][j - 1] + 1

当字符串SS的前i1i - 1个和字符串TT的前j1j - 1个匹配之后,就看第S[i]S[i]T[j]T[j]是否相同,如果相同则不需要任何操作,如果不相同,则需要将S[i]S[i]修改为T[j]T[j]

dp[i][j]=dp[i1][j1]dp[i][j] = dp[i - 1][j - 1]

dp[i][j]=dp[i1][j1]+1dp[i][j] = dp[i - 1][j - 1] + 1

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 1010;
int dp[N][N];
char a[N], b[N];
int m, n;

int main() {
    // ABCDEABCD
    // XAABZ
    scanf("%s%s", a + 1, b + 1);//下标从1开始 便于操作 
    int n = strlen(a + 1), m = strlen(b + 1);
    dp[0][0] = a[1] == b[1] ? 0 : 1;
    for (int i = 1; i <= n; i++) {
        if (a[i] == b[1]) dp[i][0] = 0;
        else dp[i][1] = dp[i - 1][0];
    }
    for (int j = 1; j <= m; j++) {
        if (a[1] == b[j]) dp[0][j] = dp[0][j - 1];
        else dp[0][j] = dp[0][j - 1] + 1;
    } 
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            dp[i][j] = min(dp[i][j - 1] + 1, dp[i - 1][j]);
            if (a[i] == b[j]) dp[i][j] = min(dp[i][j], dp[i - 1][j - 1]);
            else dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + 1);
        }
    }
    cout << dp[n][m] << endl;
    return 0;
}

G.排列数

题目

问题描述

在一个排列中,一个折点是指排列中的一个元素,它同时小于两边的元素,或者同时大于两边的元素。

对于一个11 ~ nn的排列,如果可以将这个排列中包含tt个折点,则它称为一个t+1t+1单调序列。

例如,排列(1423)(1,4,2,3)是一个33单调序列,其中4422都是折点。

给定nnkk,请问1 n1~n的所有排列中有多少个kk单调队列?

输入格式

输入一行包含两个整数nkn,k

输出格式

输出一个整数,表示答案。答案可能很大,你可需要输出满足条件的排列

数量除以123456123456的余数即可。

样例输入

42

样例输出

12

思路

设状态为dp[i][j]dp[i][j]代表前ii个数,有jj个折点我们可以发现折点的数量就是波峰+波谷的数量。

虽然推出来后转移方程很简单,但是并不是特别的好推,我们得先把各种情况先看一遍,最后总结起来你就会发现转移很简单。

对于不同的折点数量,我们分为奇数偶数考虑,再对于每种情况我们分最后一个是波峰还是波谷考虑。

然后对于ii个数,要变成i+1i+1个数,肯定是插入了i+1i+1,然后它有i+1i+1个位置可以选择。

我们发现波峰周围的22个位置是特殊的,在这里插入,折点的数量不变。

如果最后一个是波谷,那么在最后插入会不变,如果第一个也是波谷,那么在第一个位置插入也会不变(分成奇偶,和最后一个是波峰波谷就可以判断出这两种情况)。

剩下的位置插入都会增加两个折点。最后总结起来就是

dp[i+1][j]+=dp[i][j]2dp[i+1][j]+=dp[i][j]2dp\left[i+1\right]\left[j\right]+=dp\left[i\right]\left[j\right]\cdot 2dp\left[i+1\right]\left[j\right]+=dp\left[i\right]\left[j\right]∗2

dp[i+1][j+1]=dp[i][j](j+1)dp[i+1][j+1]=dp[i][j](j+1)dp\left[i+1\right]\left[j+1\right]=dp\left[i\right]\left[j\right]\cdot \left(j+1\right)dp\left[i+1\right]\left[j+1\right]=dp\left[i\right]\left[j\right]∗\left(j+1\right)

dp[i+1][j+2]=dp[i][j](i+1j12)dp[i+1][j+2]=dp[i][j](i+1j12)dp\left[i+1\right]\left[j+2\right]=dp\left[i\right]\left[j\right]\cdot \left(i+1-j-1-2\right)dp\left[i+1\right]\left[j+2\right]=dp\left[i\right]\left[j\right]∗\left(i+1−j−1−2\right)

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 505;
const int mod = 123456;
int dp[N][N];

int main() {
    int n, k;
    cin >> n >> k;
    dp[1][0] = 1;
    for (int i = 2; i <= n; i++) {
        dp[i][0] = 2;
        for (int j = 0; j <= i - 2; j++) {
            dp[i][j] %= mod;
            dp[i + 1][j] += dp[i][j] * (j + 1);
            dp[i + 1][j + 1] += dp[i][j] * 2;
            dp[i + 1][j + 2] += dp[i][j] * (i - j - 2); 
        }
    }

    cout << dp[n][k - 1] % mod << endl;
    return 0;
}

H.解谜游戏

题目

题目描述

image-20210509221519123

小明正在玩一款解谜游戏,谜题由 2424 根塑料棒组成,

其中黄色塑料棒 44 根,红色 88 根,绿色 1212 根 (后面用 YY 表示黄色、RR 表示红色、GG 表示绿色)。

初始时这些塑料棒排成三圈,如上图所示,外圈 1212 根,中圈 88 根,内圈 44 根。

小明可以进行三种操作:

将三圈塑料棒都顺时针旋转一个单位。

例如当前外圈从 00 点位置开始,顺时针依次是 YRYGRYGRGGGGYRYGRYGRGGGG,中圈是 RGRGGRRYRGRGGRRY,内圈是 GGGRGGGR。 那么顺时针旋转一次之后,外圈、中圈、内圈依次变为:GYRYGRYGRGGGGYRYGRYGRGGGYRGRGGRRYRGRGGRRRGGGRGGG

将三圈塑料棒都逆时针旋转一个单位。

例如当前外圈从 00 点位置开始,顺时针依次是 YRYGRYGRGGGGYRYGRYGRGGGG,中圈是 RGRGGRRYRGRGGRRY,内圈是 GGGRGGGR。 那么逆时针旋转一次之后,外圈、中圈、内圈依次变为:RYGRYGRGGGGYRYGRYGRGGGGYGRGGRRYRGRGGRRYRGGRGGGRG

将三圈 00 点位置的塑料棒做一个轮换。

具体来说:外圈 00 点塑料棒移动到内圈 00 点,内圈 00 点移动到中圈 00 点,中圈 00 点移动到外圈 00 点。

例如当前外圈从 00 点位置开始顺时针依次是 YRYGRYGRGGGGYRYGRYGRGGGG,中圈是RGRGGRRYRGRGGRRY,内圈是 GGGRGGGR

那么轮换一次之后,外圈、中圈、内圈依次变为:RRYGRYGRGGGGRRYGRYGRGGGGGGRGGRRYGGRGGRRYYGGRYGGR

小明的目标是把所有绿色移动到外圈、所有红色移动中圈、所有黄色移动到内圈。给定初始状态,请你判断小明是否可以达成目标?

输入格式

第一行包含一个整数 TT,代表询问的组数。(1T100)(1 ≤ T ≤ 100)

每组询问包含 33 行:

第一行包含 1212 个大写字母,代表外圈从 00 点位置开始顺时针每个塑料棒的颜色。

第二行包含 88 个大写字母,代表中圈从 00 点位置开始顺时针每个塑料棒的颜色。

第三行包含 44 个大写字母,代表内圈从 00 点位置开始顺时针每个塑料棒的颜色。

输出格式 对于每组询问,输出一行 YESYES 或者 NONO,代表小明是否可以达成目标。

样例输入

2
GYGGGGGGGGGG
RGRRRRRR
YRYY
YGGGRRRRGGGY
YGGGRRRR
YGGG

样例输出

YES
NO

思路

经过观察,每一次旋转只能三个圈一起旋转,但是边长不同,内圈四次一周,中圈八次一周,外圈十二次一周。

设初始时的棒号为内圈00-33,中圈00-77,外圈00-1111

假设内圈的00号棒转动顺时针一周(即整体顺时针转动44次)再次回到00号位置,中圈则是原来的44号棒到了00号位置;外圈则是原来的88号棒到了00号位置。整体再顺时针转动一周的话,内圈的00号位置还是初始时的00号棒,中圈也还是初始的00号棒,外圈则是44号棒;

上述依次类推,会发现内圈的每一根棒无论整体怎么转动都是跟着中圈的两根棒和外圈的三根棒的。 即内圈每一根棒都是跟中圈的两根还有外圈的三根是绑定的。

CFA1E90A-6EDF-4F72-984D-3200535D74E6 上图为例,图中蓝色框中的是绑定一起的。

同理可将整体分为四块,只需判断每块中的颜色是否能够满足即可。

代码

#include <bits/stdc++.h>
using namespace std;
int t;
string s1, s2, s3;
int main()
{
    cin >> t;
    while (t--)
    {
        int flag = 0;
        cin >> s3 >> s2 >> s1;
        for (int i = 0; i < 4; i++)
        {
            map<char, int> mp; //内圈
            mp[s1[i]] += 1;    //中圈
            mp[s2[i]] += 1;
            mp[s2[i + 4]] += 1; //外圈
            mp[s3[i]] += 1;
            mp[s3[i + 4]] += 1;
            mp[s3[i + 8]] += 1;
            if (mp['Y'] != 1 || mp['R'] != 2 || mp['G'] != 3)
            {
                flag = 1;
                break;
            }
        }
        if (flag == 0)
            cout << "YES" << endl;
        else
        {
            cout << "NO" << endl;
        }
    }
    return 0;
}

I.第八大奇迹

题目

问题描述 在一条 RR 河流域,繁衍着一个古老的名族 RR ,他们世代沿河而居,也在河边发展出了璀璨的文明。

ZZ 族在 RR 河沿岸修建了很多建筑,最近,他们热衷攀比起来,他们总是在比谁的建筑建得最奇特。

幸好 ZZ 族人对奇特的理解都差不多,他们很快给每栋建筑都打了分,这样评选谁最奇特就轻而易举了。

于是,根据分值,大家很快评出了最奇特的建筑,称为大奇迹。

后来他们又陆续评选了第二奇特、第二奇特、……、第七奇特的建筑,依次称为第二大奇迹、第三大奇迹、……、第七大奇迹。

最近,他们开始评选第八奇特的建筑,准备命名为第八大奇迹,在评选中,他们遇到了一些问题。

首先,ZZ 族一直在发展,有的建筑被拆除又建了新的建筑,新建筑的奇特值和原建筑不一样,这使得评选不那么容易了。

其次,ZZ 族的每个人所生活的范围可能不一样,他们见过的建筑并不是所有的建筑,他们坚持他们自己所看到的第八奇特的建筑就是第八大奇迹。

ZZ 族首领最近很头疼这个问题,他害怕因为意见不一致导致 ZZ 族发生分歧。他找到你,他想先了解一下,民众自己认为的奇迹是怎样的。

现在告诉在 RR 河周边的建筑的变化情况,以及在变化过程中一些人的生活范围,请编程求出每个人认为的第八大奇迹的奇特值是多少。

输入格式

输入的第一行包含两个整数 L,NL, N,分别表示河流的长度和要你处理的信息的数量。开始时河流沿岸没有建筑,或者说所有的奇特值为 00

接下来 NN 行,每行一条你要处理的信息。

如果信息为 CpxC\:\:p\:\:x,表示流域中第 pp 个位置(1pL)(1 ≤ p ≤ L) 建立了一个建筑,其奇特值为 xx。如果这个位置原来有建筑,原来的建筑会被拆除。

如果信息为 QabQ\:\:a\:\:b,表示有个人生活的范围是河流的第 aabb 个位置(包含 aababb,a ≤ b),这时你要算出这个区间的第八大奇迹的奇特值,并输出。如果找不到第八大奇迹,输出 00

输出格式 对于每个为 QQ 的信息,你需要输出一个整数,表示区间中第八大奇迹的奇特值。

样例输入

10 15
C 1 10
C 2 20
C 3 30
C 4 40
C 5 50
C 6 60
C 7 70
C 8 80
C 9 90
C 10 100
Q 1 2
Q 1 10
Q 1 8
C 10 1
Q 1 10

样例输出

0
30
10
20

数据范围

对于 20%20\% 的评测用例,1L1000,1N10001 ≤ L ≤ 1000, 1 ≤ N ≤ 1000

对于 40%40\% 的评测用例,1L10000,1N100001 ≤ L ≤ 10000, 1 ≤ N ≤ 10000

对于 100%100\% 的评测用例,1L1000001N1000001 ≤ L ≤ 100000,1 ≤ N ≤ 100000

所有奇特值为不超过 10910^9 的非负整数。

思路

排序算法

定义一个数组存放每个奇迹点的值,使用mapmap去重,数据规模是10610^6,所以不能使用O(N2)O(N^2)时间复杂度的算法 这里使用内置的sortsort函数,时间复杂度是O(NlogN)O(NlogN),不过推荐自己手写归并排序

代码

#include<bits/stdc++.h>
using namespace std;
int L, N;
vector<pair<int, int> > vec;
map<int, int> mp; 

bool cmp(pair<int, int> a, pair<int, int> b) {
    return a.second > b.second;
}

void add(int postion, int value) {
    if (mp.find(postion) != mp.end()) {
        for (int i = 0; i < vec.size(); i++) {
            if (vec[i].first == postion) {
                vec[i].second = value;
                break;
            }
        }
    } else {
        pair<int, int> p(postion, value);
        vec.push_back(p);
    }
    mp[postion] = value;
}

void sout(int start, int end) {
    vector<pair<int, int> > temp;
    for (int i = 0; i < vec.size(); i++) {
        if (vec[i].first <= end && vec[i].first >= start) 
            temp.push_back(vec[i]);
    }
    if (temp.size() < 8) {
        cout << 0 << endl;
        return;
    }
    sort(temp.begin(), temp.end(), cmp);
    cout << temp[7].second << endl;
}

int main() {
    cin >> L >> N;
    while (N--) {
        char op;
        int a, b;
        cin >> op >> a >> b;
        if (op == 'C') add(a, b);
        else sout(a, b);
    }
    return 0;
}