test
A.换零钞
题目描述
\(x\)星球的钞票的面额只有:\(100\)元,\(5\)元,\(2\)元,\(1\)元,共\(4\)种。
小明去\(x\)星旅游,他手里只有\(2\)张\(100\)元的\(x\)星币,太不方便,恰好路过\(x\)星银行就去换零钱。
小明有点强迫症,他坚持要求\(200\)元换出的零钞中\(2\)元的张数刚好是\(1\)元的张数的\(10\)倍,剩下的当然都是\(5\)元面额的。
银行的工作人员有点为难,你能帮助算出:在满足小明要求的前提下,最少要换给他多少张钞票吗?
(\(5\)元,\(2\)元,\(1\)元面额的必须都有,不能是\(0\))
思路
这道题目是数学题目,设一元的张数是\(x\),两元的张数为\(10x\),五元的张数为\(\frac{\left(200-x-10x\:\cdot \:\:2\right)}{5}\)
可以的到所有张数的函数表达式\(f\left(x\right)\:=\:\frac{\left(200-x-10x\:\cdot \:\:\:2\right)}{5}+11x\)
进一步化简可以得:\(f\left(x\right)\:=\:40+\frac{34x}{5}\)
由于题目需要最小张数,且需要为整数,所以\(x=5\)时,\(f\left(x\right)\)可以取的最小值\(74\)
代码
#include<bits/stdc++.h>
using namespace std;
int main() {
cout << 74 << endl;
return 0;
}
B.激光样式
题目描述
\(x\)星球的盛大节日为增加气氛,用\(30\)台机光器一字排开,向太空中打出光柱。
安装调试的时候才发现,不知什么原因,相邻的两台激光器不能同时打开!
国王很想知道,在目前这种\(bug\)存在的情况下,一共能打出多少种激光效果?
显然,如果只有\(3\)台机器,一共可以成\(5\)种样式,即:
全都关上(\(sorry\), 此时无声胜有声,这也算一种)
开一台,共\(3\)种
开两台,只\(1\)种
\(30\)台就不好算了,国王只好请你帮忙了。
思路
动态规划DP
- 用0表示激光灯灭
- 用1表示激光灯亮
一盏灯的时候可以是\(0\),\(1\)即灭或者亮;
两盏灯的时候可以是\(00\),\(01\),\(10\)(并排)
三盏灯的时候可以是\(000\),\(010\),\(100\),\(001\),\(101\)(并排)
因此可以看出三盏灯的样式种数是由两部分组成的,
- 第一部分就是,前面两盏灯不管亮不亮,第三盏灯都不亮
- 第二部分就是,第二盏灯灭的前提下,第三盏灯亮,而第二盏灯灭的样式种数等于只有一盏灯时候的样式种数。
\(dp[i]\)表示一共有\(i\)盏灯时候的样式种类
可以得到DP初始条件: \(dp\left[1\right]\:=\:2,\:dp\left[2\right]\:=\:3\)
状态转移方程:\(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开始就不行了。
从下一个开始逆向推理 \(0101\:XOR\:0111\:=\:0010\) 因此需要想办法把0111 变成 0010
0111 取其负数 1001 再 \(XOR\) 即可得到 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 星云的一个小时有 \(n\) 分钟。
大家都知道,手表只有一个按钮可以把当前的数加一。在调分钟的时候,如果当前显示的数是 \(0\) ,那么按一下按钮就会变成 \(1\),再按一次变成 \(2\) 。如果当前的数是 \(n - 1\),按一次后会变成 \(0\)。
作为强迫症患者,小明一定要把手表的时间调对。如果手表上的时间比当前时间多\(1\),则要按 \(n - 1\) 次加一按钮才能调回正确时间。
小明想,如果手表可以再添加一个按钮,表示把当前的数加 \(k\) 该多好啊……
他想知道,如果有了这个 \(+k\) 按钮,按照最优策略按键,从任意一个分钟数调到另外任意一个分钟数最多要按多少次。
注意,按 \(+k\) 按钮时,如果加\(k\)后数字超过\(n-1\),则会对\(n\)取模。
比如,\(n=10\), \(k=6\) 的时候,假设当前时间是\(0\),连按\(2\)次 \(+k\) 按钮,则调为\(2\)。
输入
两个整数 \(n\), \(k\) ,意义如题。
\(0 < k < n <= 100000\)
输出
一行一个整数
表示:按照最优策略按键,从一个时间调到另一个时间最多要按多少次。
思路
动态规划DP
题目要求是最优策略,所以一定要求+i的时候,是最少次数的,每次只能+1或者+k,从一个时刻调整到另一个时刻,需要加上\(\left[0,\:n\:-\:1\right]\)
\(dp[i]\)表示\(+i\)需要的次数,默认只有一个\(+1\)按钮,所以直接初始化\(dp[i] = i\);
其次需要初始化\(+k\)的次数\(dp\left[i\right]\:=\:dp\left[i\:-\:k\right]\:+\: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.搭积木
题目描述
小明对搭积木非常感兴趣。他的积木都是同样大小的正立方体。
在搭积木时,小明选取 \(m\) 块积木作为地基,将他们在桌子上一字排开,中间不留空隙,并称其为第0层。
随后,小明可以在上面摆放第\(1\)层,第\(2\)层,……,最多摆放至第\(n\)层。摆放积木必须遵循三条规则:
规则\(1\):每块积木必须紧挨着放置在某一块积木的正上方,与其下一层的积木对齐;
规则\(2\):同一层中的积木必须连续摆放,中间不能留有空隙;
规则\(3\):小明不喜欢的位置不能放置积木。
其中,小明不喜欢的位置都被标在了图纸上。图纸共有\(n\)行,从下至上的每一行分别对应积木的第1层至第\(n\)层。
每一行都有\(m\)个字符,字符可能是‘.’或‘X’,其中‘X’表示这个位置是小明不喜欢的。
现在,小明想要知道,共有多少种放置积木的方案。他找到了参加蓝桥杯的你来帮他计算这个答案。
由于这个答案可能很大,你只需要回答这个答案对\(1000000007\)(十亿零七)取模后的结果。
注意:地基上什么都不放,也算作是方案之一种。
输入
输入数据的第一行有两个正整数n和m,表示图纸的大小。\(n<=100\),\(m<=100\)
随后n行,每行有\(m\)个字符,用来描述图纸 。每个字符只可能是‘.’或‘X’。
输出
输出一个整数,表示答案对\(1000000007\)取模后的结果。
样例输入
2 3
..X
.X.
样例输出
4
样例解释
成功的摆放有(其中O表示放置积木):
(1)
..X
.X.
(2)
..X
OX.
(3)
O.X
OX.
(4)
..X
.XO
思路
动态规划
\(check[i][j]\):表示第\(i\)层前\(j\)个中有多少个‘X’
\(dp[l][r] = v\):表示当前层中的\([l,r]\)的方法数是\(v\)
\(check\)其实就是前缀和操作,对每一层进行前缀和运算
我们规定积木从下到上分别是第\(n\)层,第\(n-1\)层,最上面是第一层
我们首先用\(check\)来更新最低层\(dp\)的值
然后从最低层开始向上传递,即从大区间枚举到小区间后得出的方法数
转移方程是:\(dp[l][r]+=dp[l-1][r]+dp[l][r+1]-dp[l-1][r+1]\)
因为我们的l和r分别是从两端开始向内走,所以我们更新区间时也是由外向内更新,当更新区间\([l,r]\)时,我们已知的值区间\([l-1][r]\)和区间\([l][r+1]\)区间\([l-1][r+1]\),因为\(dp[l-1][r]\)和dp\([l][r+1]\)中包括的是\([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.矩阵求和
题目描述
经过重重笔试面试的考验,小明成功进入 \(Macrohard\) 公司工作。
今天小明的任务是填满这么一张表:
表有 \(n\) 行 \(n\) 列,行和列的编号都从\(1\)算起。
其中第 \(i\) 行第 \(j\) 个元素的值是 \(gcd(i, j)\)的平方,
\(gcd\) 表示最大公约数,以下是这个表的前四行的前四列:
1 1 1 1
1 4 1 4
1 1 9 1
1 4 1 16
小明突然冒出一个奇怪的想法,他想知道这张表中所有元素的和。
输入
一行一个正整数 \(n\) 意义见题。\(n <= 10^7\)
输出
一行一个数,表示所有元素的和。由于答案比较大,请输出模 (\(10^9 + 7\))(即:十亿零七) 后的结果
输入
\(4\)
输出
\(48\)
思路
这道题暴力思路必定TLE
搜了一下大佬的思路,需要用到欧拉函数和莫比乌斯反演来做
这道题公式表达式\(ans\:=\sum _{i=1}^n\:\sum _{i=1}^n\:gcd\left(i,\:j\right)^2\)
\(gcd\left(i,\:j\right)\:=\:d\:\)表示\(\:\left(i,\:j\right)\:\)的最大公约数为\(\:d\)
\(\:count\left(d\right)\:\)则表示最大公约数为\(\:d\:\)的数对的个数
\(count\left(d\right)\:\cdot \:d\:\cdot \:d\:\)则求解的为最大公约数为\(\:d\:\)的所有数的和,此时问题的核心则转化为如何求\(\:count\left(d\right)\)。
\(𝑔𝑐𝑑\left(𝑖,\:𝑗\right)=𝑑,\:𝑔𝑐𝑑\left(\frac{𝑖}{d},\:\frac{𝑗}{d}\right)=1\)
令\(\:𝑖=\frac{𝑖}{d},\:𝑗=\frac{𝑗}{d}。\)代入则\(\:𝑔𝑐𝑑\left(𝑖,𝑗\right)=1。\)
最大公约数的取值范围为 \([1, n]\),\(d\) 为 \([1, n]\) 中的任意一个数。
\(i\), \(j\)的取值在 \([1, n / d]\),求解该区间内所有互质对的个数, 而该题的数据范围比较大,故采用筛法求欧拉函数(线性筛法)即 \(1 - n\) 中的欧拉函数,时间复杂度能控制在 \(𝑂(𝑛)\)。

\(𝑠\left[𝑛\right]=\sum _{i\:=\:2}^n\:𝐸𝑢𝑙𝑒𝑟\left(𝑖\right)×2+1\:\)
推导到如下:
\(𝑠\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;
}

浙公网安备 33010602011771号