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