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

795 阅读2分钟

A.换零钞

题目描述

xx星球的钞票的面额只有:100100元,55元,22元,11元,共44种。

小明去xx星旅游,他手里只有22100100元的xx星币,太不方便,恰好路过xx星银行就去换零钱。

小明有点强迫症,他坚持要求200200元换出的零钞中22元的张数刚好是11元的张数的1010倍,剩下的当然都是55元面额的。

银行的工作人员有点为难,你能帮助算出:在满足小明要求的前提下,最少要换给他多少张钞票吗?

55元,22元,11元面额的必须都有,不能是00

思路

这道题目是数学题目,设一元的张数是xx,两元的张数为10x10x,五元的张数为(200x10x2)5\frac{\left(200-x-10x\:\cdot \:\:2\right)}{5}

可以的到所有张数的函数表达式f(x)=(200x10x2)5+11xf\left(x\right)\:=\:\frac{\left(200-x-10x\:\cdot \:\:\:2\right)}{5}+11x

进一步化简可以得:f(x)=40+34x5f\left(x\right)\:=\:40+\frac{34x}{5}

由于题目需要最小张数,且需要为整数,所以x=5x=5时,f(x)f\left(x\right)可以取的最小值7474

代码

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

int main() {
    cout << 74 << endl;
    return 0;
}

B.激光样式

题目描述

xx星球的盛大节日为增加气氛,用3030台机光器一字排开,向太空中打出光柱。

安装调试的时候才发现,不知什么原因,相邻的两台激光器不能同时打开!

国王很想知道,在目前这种bugbug存在的情况下,一共能打出多少种激光效果?

显然,如果只有33台机器,一共可以成55种样式,即:

全都关上(sorrysorry, 此时无声胜有声,这也算一种)

开一台,共33

开两台,只11

3030台就不好算了,国王只好请你帮忙了。

思路

动态规划DP

  • 用0表示激光灯灭
  • 用1表示激光灯亮

一盏灯的时候可以是0011即灭或者亮; 两盏灯的时候可以是000001011010(并排) 三盏灯的时候可以是000000010010100100001001101101(并排)

因此可以看出三盏灯的样式种数是由两部分组成的,

  • 第一部分就是,前面两盏灯不管亮不亮,第三盏灯都不亮
  • 第二部分就是,第二盏灯灭的前提下,第三盏灯亮,而第二盏灯灭的样式种数等于只有一盏灯时候的样式种数。

dp[i]dp[i]表示一共有ii盏灯时候的样式种类

可以得到DP初始条件: dp[1]=2,dp[2]=3dp\left[1\right]\:=\:2,\:dp\left[2\right]\:=\:3

状态转移方程:dp[i]=dp[i1]+dp[i2](i3)dp\left[i\right]\:=\:dp\left[i\:-\:1\right]\:+\:dp\left[i\:-\:2\right]\:\left(i\:\ge \:3\right)

代码

#include<bits/stdc++.h>
using namespace std;
int main() {
    int dp[31];
    dp[1] = 2;
    dp[2] = 3;
    for (int i = 3; i <= 30; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }   
    cout << dp[30] << endl;
    return 0;
}

C.格雷码

题目描述

格雷码是以n位的二进制来表示数。

与普通的二进制表示不同的是,它要求相邻两个数字只能有1个数位不同。

首尾两个数字也要求只有1位之差。

有很多算法来生成格雷码。以下是较常见的一种:

从编码全0开始生成。

当产生第奇数个数时,只把当前数字最末位改变(0变1,1变0)

当产生第偶数个数时,先找到最右边的一个1,把它左边的数字改变。

用这个规则产生的4位格雷码序列如下:

0000
0001
0011
0010
0110
0111
0101
0100
1100
1101
1111
1110
1010
1011
1001
1000

思路

答案:a = a ^ ((a & (-a)) << 1); //填空

从前面的很明显可以看出,(a << 1) ^ a就可以了,但是后面从0111开始就不行了。

从下一个开始逆向推理 0101XOR0111=00100101\:XOR\:0111\:=\:0010 因此需要想办法把0111 变成 0010 0111 取其负数 1001XORXOR 即可得到 0010 带入其他奇数验证,成立。

代码

#include <stdio.h>
void show(int a,int n)
{
	int i;
	int msk = 1;
	for(i=0; i<n-1; i++) msk = msk << 1;
	for(i=0; i<n; i++){
		printf((a & msk)? "1" : "0");
		msk = msk >> 1;
	}
	printf("\n");
} 

void f(int n)
{
	int i;
	int num = 1;
	for(i=0; i<n; i++) num = num<<1;
	
	int a = 0;
	for(i=0; i<num; i++){
		show(a,n);
		
		if(i%2==0){
			a = a ^ 1;
		}
		else{
			a = a ^ ((a & (-a)) << 1) ; //填空
		}
	}
}

int main()
{
	f(4);
	return 0;
}

D.调手表

题目描述

小明买了块高端大气上档次的电子手表,他正准备调时间呢。

在 M78 星云,时间的计量单位和地球上不同,M78 星云的一个小时有 nn 分钟。

大家都知道,手表只有一个按钮可以把当前的数加一。在调分钟的时候,如果当前显示的数是 00 ,那么按一下按钮就会变成 11,再按一次变成 22 。如果当前的数是 n1n - 1,按一次后会变成 00

作为强迫症患者,小明一定要把手表的时间调对。如果手表上的时间比当前时间多11,则要按 n1n - 1 次加一按钮才能调回正确时间。

小明想,如果手表可以再添加一个按钮,表示把当前的数加 kk 该多好啊……

他想知道,如果有了这个 +k+k 按钮,按照最优策略按键,从任意一个分钟数调到另外任意一个分钟数最多要按多少次。

注意,按 +k+k 按钮时,如果加kk后数字超过n1n-1,则会对nn取模。

比如,n=10n=10, k=6k=6 的时候,假设当前时间是00,连按22+k+k 按钮,则调为22

输入

两个整数 nn, kk ,意义如题。 0<k<n<=1000000 < k < n <= 100000

输出

一行一个整数 表示:按照最优策略按键,从一个时间调到另一个时间最多要按多少次。

思路

动态规划DP

题目要求是最优策略,所以一定要求+i的时候,是最少次数的,每次只能+1或者+k,从一个时刻调整到另一个时刻,需要加上[0,n1]\left[0,\:n\:-\:1\right]

dp[i]dp[i]表示+i+i需要的次数,默认只有一个+1+1按钮,所以直接初始化dp[i]=idp[i] = i;

其次需要初始化+k+k的次数dp[i]=dp[ik]+1dp\left[i\right]\:=\:dp\left[i\:-\:k\right]\:+\:1

状态转移方程式

dp[i]=min(dp[(i+nk)%n]+1,dp[i1]+1);dp\left[i\right]\:=\:min\left(dp\left[\left(i\:+\:n\:-\:k\right)\:\%\:n\right]\:+\:1,\:dp\left[i\:-\:1\right]\:+\:1\right);

代码

#include<bits/stdc++.h>
using namespace std;
int dp[100005];
int main() {
    int n, k;
    cin >> n >> k;
    // 初始化dp初始状态
    for (int i = 0; i < n; i++) {
        dp[i] = i; // 初始化为多少分钟需要调整多少次,默认为一分钟调整一次;
    }
    for (int i = k; i < n; i += k) {
        dp[i] = dp[i - k] + 1; // 从0开始,每过k步 就+1
    }
    int sum = -1;
    while (1) { // 如果结果是收敛的,则停止循环。
        int now_sum = 0;
        for (int i = 1; i < n; i ++) {
            now_sum += dp[i];
            // 状态转移方程式
            dp[i] = min(dp[(i + n - k) % n] + 1, dp[i - 1] + 1);
        }
        if (now_sum == sum) break;
        sum = now_sum;
    }
    int ans = dp[0];
    for (int i = 0; i < n; i++) {
        ans = max(ans, dp[i]);
    }
    cout << ans << endl;
    return 0;
}

E.搭积木

题目描述

小明对搭积木非常感兴趣。他的积木都是同样大小的正立方体。

在搭积木时,小明选取 mm 块积木作为地基,将他们在桌子上一字排开,中间不留空隙,并称其为第0层。

随后,小明可以在上面摆放第11层,第22层,……,最多摆放至第nn层。摆放积木必须遵循三条规则:

规则11:每块积木必须紧挨着放置在某一块积木的正上方,与其下一层的积木对齐;

规则22:同一层中的积木必须连续摆放,中间不能留有空隙;

规则33:小明不喜欢的位置不能放置积木。

其中,小明不喜欢的位置都被标在了图纸上。图纸共有nn行,从下至上的每一行分别对应积木的第1层至第nn层。

每一行都有mm个字符,字符可能是‘.’或‘X’,其中‘X’表示这个位置是小明不喜欢的。

现在,小明想要知道,共有多少种放置积木的方案。他找到了参加蓝桥杯的你来帮他计算这个答案。

由于这个答案可能很大,你只需要回答这个答案对10000000071000000007(十亿零七)取模后的结果。

注意:地基上什么都不放,也算作是方案之一种。

输入

输入数据的第一行有两个正整数n和m,表示图纸的大小。n<=100n<=100m<=100m<=100

随后n行,每行有mm个字符,用来描述图纸 。每个字符只可能是‘.’或‘X’。

输出

输出一个整数,表示答案对10000000071000000007取模后的结果。

样例输入

2 3

..X

.X.

样例输出

4

样例解释

成功的摆放有(其中O表示放置积木):

(1)
..X
.X.
(2)
..X
OX.
(3)
O.X
OX.
(4)
..X
.XO

思路

动态规划

check[i][j]check[i][j]:表示第ii层前jj个中有多少个‘X’

dp[l][r]=vdp[l][r] = v:表示当前层中的[l,r][l,r]的方法数是vv

checkcheck其实就是前缀和操作,对每一层进行前缀和运算

我们规定积木从下到上分别是第nn层,第n1n-1层,最上面是第一层

我们首先用checkcheck来更新最低层dpdp的值

然后从最低层开始向上传递,即从大区间枚举到小区间后得出的方法数

转移方程是:dp[l][r]+=dp[l1][r]+dp[l][r+1]dp[l1][r+1]dp[l][r]+=dp[l-1][r]+dp[l][r+1]-dp[l-1][r+1]

因为我们的l和r分别是从两端开始向内走,所以我们更新区间时也是由外向内更新,当更新区间[l,r][l,r]时,我们已知的值区间[l1][r][l-1][r]和区间[l][r+1][l][r+1]区间[l1][r+1][l-1][r+1],因为dp[l1][r]dp[l-1][r]和dp[l][r+1][l][r+1]中包括的是[l1,r+1]+[l,r][l-1,r+1]+[l,r]所以要减去

代码

#include<bits/stdc++.h>
using namespace std;
int mod = 1e9 + 7;
typedef long long ll;
const int maxn = 110;
ll dp[maxn][maxn];
int check[maxn][maxn];
int main() {
    int m, n;
    char str[maxn];
    cin >> n >> m;
    // 读入数据
    for (int i = 1; i <= n; i++) {
        scanf("%s", str + 1);
        for (int j = 1; j <= m; j++) {
            // check 预处理 第i层 中前j个 有多少个X
            check[i][j] = check[i][j - 1];
            if (str[j] == 'X') check[i][j]++;
        }
    }
    // 默认也算一种结果
    ll ans = 1;
    // 初始化,最底层的数据,采用两边向中间靠拢
    for (int i = 1; i <= m; i++) {
        for (int j = m; j >= i; j--) {
            // check[n][j] - check[n][i - 1] == 0相当于没有X可以算一种
            if (check[n][j] - check[n][i - 1] == 0) {
                ans ++;
                // l=i r=j 这个区间可以的种数 = [l=i][r=j + 1] + [l=i - 1][j] - [l=i - 1][r=j + 1] + 1
                dp[i][j] = dp[i][j + 1] + dp[i - 1][j] - dp[i - 1][j + 1] + 1;
            }
        }
    }

    // 开始状态转移,循环层数
    for (int t = n - 1; t > 0; t--) {
        // 像初始化底层数据一样有相同的状态转移表达式
        // 枚举每一层的所有情况
        for (int i = 1; i <= m; i++) {
            for (int j = m; j >= i; j--) {
                // [i,j]没有X 向上递推
                // [i,j]含有X 上面则不能堆放积木
                if (check[t][j] - check[t][i - 1] == 0) {
                    ans = (ans + dp[i][j]) % mod;
                    dp[i][j] = (dp[i][j] + dp[i - 1][j] + dp[i][j + 1] - dp[i - 1][j + 1]) % mod;
                } else dp[i][j] = 0;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

F.矩阵求和

题目描述

经过重重笔试面试的考验,小明成功进入 MacrohardMacrohard 公司工作。

今天小明的任务是填满这么一张表:

表有 nnnn 列,行和列的编号都从11算起。

其中第 ii 行第 jj 个元素的值是 gcd(i,j)gcd(i, j)的平方,

gcdgcd 表示最大公约数,以下是这个表的前四行的前四列:

1  1  1  1
1  4  1  4
1  1  9  1
1  4  1 16

小明突然冒出一个奇怪的想法,他想知道这张表中所有元素的和。

输入

一行一个正整数 nn 意义见题。n<=107n <= 10^7

输出

一行一个数,表示所有元素的和。由于答案比较大,请输出模 (109+710^9 + 7)(即:十亿零七) 后的结果

输入

44

输出

4848

思路

这道题暴力思路必定TLE 搜了一下大佬的思路,需要用到欧拉函数莫比乌斯反演来做

这道题公式表达式ans=i=1ni=1ngcd(i,j)2ans\:=\sum _{i=1}^n\:\sum _{i=1}^n\:gcd\left(i,\:j\right)^2

gcd(i,j)=dgcd\left(i,\:j\right)\:=\:d\:表示(i,j)\:\left(i,\:j\right)\:的最大公约数为d\:d

count(d)\:count\left(d\right)\:则表示最大公约数为d\:d\:的数对的个数

count(d)ddcount\left(d\right)\:\cdot \:d\:\cdot \:d\:则求解的为最大公约数为d\:d\:的所有数的和,此时问题的核心则转化为如何求count(d)\:count\left(d\right)

𝑔𝑐𝑑(𝑖,𝑗)=𝑑,𝑔𝑐𝑑(𝑖d,𝑗d)=1𝑔𝑐𝑑\left(𝑖,\:𝑗\right)=𝑑,\:𝑔𝑐𝑑\left(\frac{𝑖}{d},\:\frac{𝑗}{d}\right)=1

𝑖=𝑖d,𝑗=𝑗d\:𝑖=\frac{𝑖}{d},\:𝑗=\frac{𝑗}{d}。代入则𝑔𝑐𝑑(𝑖,𝑗)=1\:𝑔𝑐𝑑\left(𝑖,𝑗\right)=1。

最大公约数的取值范围为 [1,n][1, n]dd[1,n][1, n] 中的任意一个数。

ii, jj的取值在 [1,n/d][1, n / d],求解该区间内所有互质对的个数, 而该题的数据范围比较大,故采用筛法求欧拉函数(线性筛法)即 1n1 - n 中的欧拉函数,时间复杂度能控制在 𝑂(𝑛)𝑂(𝑛)

img

𝑠[𝑛]=i=2n𝐸𝑢𝑙𝑒𝑟(𝑖)×2+1𝑠\left[𝑛\right]=\sum _{i\:=\:2}^n\:𝐸𝑢𝑙𝑒𝑟\left(𝑖\right)×2+1\:

推导到如下:

𝑠[𝑖]=𝑠[𝑖1]+2×𝐸𝑢𝑙𝑒𝑟[𝑖]𝑠\left[𝑖\right]=𝑠\left[𝑖−1\right]+2×𝐸𝑢𝑙𝑒𝑟\left[𝑖\right]

代码

#include <iostream>
using namespace std;

typedef long long LL;
const int N = 1e7 + 10, mod = 1e9 + 7;
int n, cnt;
LL primes[N], eulers[N], s[N];
bool st[N];

// 线性筛法 
void get_eulers(int n)
{ 
    eulers[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!st[i]) {
            primes[cnt++] = i;
            // i 为质数 
            eulers[i] = i - 1;
        }
        // 筛非质数 
        for (int j = 0; primes[j] <= n / i; ++j) {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0) {
                eulers[t] = eulers[i] * primes[j];
                break;
            }
            eulers[t] = eulers[i] * (primes[j] - 1);
        }
    }
    // 开 LL,避免溢出  
    s[1] = 1; 
    // 递推求解 s[i]
    for (int i = 2; i <= n; ++i) {
        s[i] = s[i - 1] + 2 * eulers[i];
    } 
}

int main()
{
    cin >> n;
    get_eulers(n);
    int res = 0;
    for (int d = 1; d <= n; ++d) {
        res = (res + (LL) s[n / d] * d % mod * d) % mod;
    }
    cout << res << endl;
    return 0;
}