A.Long Loong(格式输出)
题意:
给出一个正整数
X
X
X,请你输出一个'L'
,然后
X
X
X个'o'
,最后各输出一个'n'
和'g'
。
分析:
按要求输出即可。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
int n;
cin >> n;
cout << 'L';
for (int i = 0; i < n; i++) cout << 'o';
cout << "ng" << endl;
}
int main() {
solve();
return 0;
}
B.CTZ(二进制)
题意:
给出一个正整数
X
X
X,请你求出
X
X
X在二进制下末尾有多少个连续的0
。
分析:
循环除 2 2 2直到输入的 X X X变为奇数为止,循环的次数即为答案。
优化
直接使用函数计算答案,答案为 l o g 2 l o w b i t ( X ) log_2^{lowbit(X)} log2lowbit(X)
Tips:系统封装了库函数 l o g 2 ( ) log2() log2()可用于计算 2 2 2为底数的对数, l o w b i t lowbit lowbit则需自己实现
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
int n;
cin >> n;
int ans = 0;
while (n > 0 && n % 2 == 0) {
ans++;
n /= 2;
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
C.Even Digits(思维)
题意:
仅能使用0, 2, 4, 6, 8
作为十进制上每一位数字的选择,问能组成的第
N
N
N小的数字是多少(从小到大)。
分析:
不难发现,选择完最高位的数字后,假设后面还有 m m m位十进制数字需要选择,此时的方案总数共 5 k 5^{k} 5k种。
那么,可以先找到最高位的数字是多少,即先计算出第一个满足 ( m + 1 ) = 5 k ≥ N (m + 1) = 5^{k} \ge N (m+1)=5k≥N的 m m m,然后将 m m m作为除数依次计算得到每一位的数字即可。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
ll n;
cin >> n;
n--;
ll m = 5;
while (m <= n) m *= 5;
m /= 5;
while (m > 0) {
cout << (n / m) * 2;
n %= m;
m /= 5;
}
cout << endl;
}
int main() {
solve();
return 0;
}
D.Pyramid(DP)
题意:
金字塔定义:一个高度为 k k k的金字塔序列共包含 ( 2 k − 1 ) (2k - 1) (2k−1)个数字,且序列格式为: 1 , 2 , . . . , k − 1 , k , k − 1 , . . . , 2 , 1 1, 2, ..., k - 1, k, k - 1, ..., 2, 1 1,2,...,k−1,k,k−1,...,2,1。
给出一个包含 n n n个数字的序列 A = ( A 1 , A 2 , . . . , A n ) A = (A_1, A_2, ..., A_n) A=(A1,A2,...,An),你可以进行若干次以下操作:
-
选择一个序列中的数字,将该数字减少 1 1 1
-
移除序列开头或末尾的数字
问,能获得的所有金字塔序列中,高度最高的金字塔的高度是多少?
分析:
将金字塔序列从中心分成两半,使用 p r e [ i ] , n x t [ i ] pre[i], nxt[i] pre[i],nxt[i]分别表示以第 i i i个数字作为金字塔中心,能组成的最长前半和后半序列的长度。
对于以第 i i i个数字作为金字塔中心,需要考虑两种情况:
-
前/后面序列构造的金字塔高度很小,那么当前中心点再高也只能通过减少高度到前/后面序列的最高高度加一。
-
前/后面序列构造的金字塔高度很大,那么只能通过删除前/后面部分数字,然后将接近中心的部分数字依次减少1,此时的金字塔高度即为当前数字。
依次计算得到 p r e , n x t pre, nxt pre,nxt数组,再枚举所有点为中心,此时选择第 i i i个数字为中心,能构造的最高金字塔高度即为 m i n ( p r e [ i ] , n x t [ i ] ) min(pre[i], nxt[i]) min(pre[i],nxt[i]),记录所有能构造的金字塔中的最高高度即为答案。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5e2;
int n, a[N], pre[N], nxt[N];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
pre[i] = min(pre[i - 1] + 1, a[i]);
}
for (int i = n; i >= 1; i--) {
nxt[i] = min(nxt[i + 1] + 1, a[i]);
}
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, min(pre[i], nxt[i]));
}
cout << ans << endl;
}
int main() {
solve();
return 0;
}
E.Digit Sum Divisible(思维,枚举,dp)
题意:
对于一个数字 x x x,我们定义它各数位上数字的和为 s s s,如 x = 2024 , s = 2 + 0 + 2 + 4 = 8 x=2024, s=2+0+2+4=8 x=2024,s=2+0+2+4=8。现在我们想知道 1 − N 1-N 1−N之间有多少个数字 x x x满足 x x x是 s s s的倍数。
分析:
本题 N ≤ 1 0 14 N\le10^{14} N≤1014,即我们可以认为 s ≤ 14 × 9 = 126 s\le14\times9=126 s≤14×9=126,即:无论如何, s s s最多也不会超过 126 126 126,因此,我们可以对满足 1 ≤ s ≤ 126 1\le s\le126 1≤s≤126和 x ≤ N x\le N x≤N的数进行枚举,枚举的过程是通过枚举各个数位上的数来实现的。为了便于获取每一位上的数,我们可以将 N N N看做一个字符串, N [ d ] N[d] N[d]代表从高往低第 d d d位的值。
对于每一个可能的目标 t a r g e t target target,我们设置 d p dp dp数组, d p [ d ] [ c u r ] [ r m d ] dp[d][cur][rmd] dp[d][cur][rmd]表示:现在我们对第 d d d位的数进行枚举,之前枚举结果的数位和为 c u r cur cur,除以 t a r g e t target target的余数为 r m d rmd rmd的方案数。那么很容易想到:假设第 d d d位上我们放置 t t t,那么 d p [ d + 1 ] [ c u r + t ] [ ( r m d ∗ 10 + t ) % t a r g e t ] dp[d+1][cur+t][(rmd*10+t)\%target] dp[d+1][cur+t][(rmd∗10+t)%target]与 d p [ d ] [ c u r ] [ r m d ] dp[d][cur][rmd] dp[d][cur][rmd]的值直接相关,需要进行更新。
但是,本题有一个限制:我们构建的数字不能超过 N N N,那么当我们构建到第 d d d位时,就要根据之前构建的情况进行分类讨论。若我们构建出的数字从第 0 0 0位到第 d − 1 d-1 d−1位都和 N N N一模一样(称为 f l a g flag flag条件),那么我们根据枚举对象 t t t的大小,进行分类讨论:
大小关系 | 代表含义 |
---|---|
t < N [ d ] t < N[d] t<N[d] | 枚举对象没有超过 n n n在这一位上的内容,低位可以是任意数字(都不会超过 n n n) |
t = N [ d ] t = N[d] t=N[d] | 枚举对象到第 d d d位仍然保持一致,继续传递下去,向后讨论 |
t > N [ d ] t > N[d] t>N[d] | 枚举对象超过了 n n n在这一位上的内容,不可能,无需考虑这一情况 |
而若 f l a g flag flag条件不满足,则后面的位置上可以随意枚举任意的数。因此我们增添一个维度 f l a g flag flag,来进行更新。当 d d d与 N N N的长度一致,且 r m d = 0 rmd=0 rmd=0(没有余数,是满足要求的数字),且 c u r = t a r g e t cur=target cur=target(创建出的数字数位和,与目标数位和一致)时,代表出现了一种新的方案。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[20][300][300][5];
string n;
int a[20];
ll dfs(int target, int d, int cur, int rmd, int flag) {
// 如果d超过n的位数,或者是i超过枚举目标target,都说明无解
if (d > n.size() || cur > target)return 0;
/*
如果d已经对应了n位数(可能有前导0,这是没关系的),
且余数为0(可以整除),且目前的和cur与目标和target一致,那说明有一种可行方案
*/
if (d == n.size() && rmd == 0 && cur == target)return dp[d][cur][rmd][flag] = 1;
if (dp[d][cur][rmd][flag] != -1)return dp[d][cur][rmd][flag];
ll res = 0;
//在枚举第d位的时候并不是任何数字都可以的,
//还要考虑之前枚举的数与 n的对应位置上的数a[]是否一致
//若一致的话,这里就只能填写0~a[d]范围内的数
for (int t = 0; t <= 9; t++) {
if (flag) {
if (t > a[d])break;
else if (t == a[d]) {
//往下一位搜索的时候,需要传递【之前的每一位都和n的对应位置一致】这一信息
res += dfs(target, d + 1, cur + t, (rmd * 10 + t) % target, 1);
} else {
//这一位数字比N小,之后的数字可以任意枚举
res += dfs(target, d + 1, cur + t, (rmd * 10 + t) % target, 0);
}
} else {
//高位数字均比N小,之后的数字可以任意枚举
res += dfs(target, d + 1, cur + t, (rmd * 10 + t) % target, 0);
}
}
return dp[d][cur][rmd][flag] = res;
}
int main() {
cin >> n;
for (int i = 0; i < n.size(); i++) {
a[i] = n[i] - '0';
}
ll ans = 0;
for (int s = 1; s <= n.size() * 9; s++) {
memset(dp, -1, sizeof(dp));
ans += dfs(s, 0, 0, 0, 1);
}
cout << ans << endl;
return 0;
}
F.Rotation Puzzle(折半搜索)
题意:
有一个 H H H行 W W W列的格栅图,对应着数字 1 1 1到 H × W H\times W H×W,即格子 ( i , j ) (i,j) (i,j)对应着 1 ≤ S i , j ≤ H × W 1\le S_{i,j}\le H\times W 1≤Si,j≤H×W。我们现在希望在若干次操作以后,能够使得 S i , j = ( i − 1 ) × W + j S_{i,j}=(i-1)\times W+j Si,j=(i−1)×W+j即从左到右、从上到下,依次为 1 , 2 , … , H × W 1,2,\dots,H\times W 1,2,…,H×W。你所能做的操作是这样的:
- 首先,在格栅图中,选定一个大小为 ( H − 1 ) × ( W − 1 ) (H-1)\times(W-1) (H−1)×(W−1)的矩形范围;
- 将其中的内容进行 180 ° 180° 180°旋转,例如:
1 2 3 对应: 6 5 4
4 5 6 ------> 3 2 1
如果你能在 20 20 20次操作内将矩阵转换成理想状态,则输出最少的步数,否则输出 − 1 -1 −1。
分析:
根据题目所说的旋转区域大小,我们可以认为只存在 4 4 4种方案,分别对应左上角、左下角、右上角、右下角,对应 20 20 20步,就是 4 × 3 19 4\times3^{19} 4×319(因为连续旋转同一个区域 2 2 2次是没有意义的),这个数字超过了 4 × 1 0 9 4\times10^9 4×109,仍然会超时。
我们注意到这个过程中指数有很大的影响,此时我们想到可以使用折半搜索
:因为我们知道起始状态,也知道目标状态,那么我们可以分别将起始状态向后进行变化,将目标状态向前进行还原,此时对于起始状态,它进行
10
10
10次旋转对应的方案数为
4
×
3
9
4\times3^9
4×39,不超过
×
1
0
5
\times10^5
×105种可能,此时时间复杂度就大大下降了。在枚举过程中,我们使用广度有限搜索,记录步数与对应的旋转结果。
在实现旋转过程时,假设 ( i , j ) (i,j) (i,j)与 ( k , l ) (k,l) (k,l)对应,那么我们有: i + k = 2 ∗ x + h − 2 i+k=2*x+h-2 i+k=2∗x+h−2,以及 j + l = 2 ∗ y + w − 2 j+l=2*y+w-2 j+l=2∗y+w−2,其中 ( x , y ) (x,y) (x,y)是旋转区域左上角的下标。这样以来我们经过代数计算即可找到旋转后的对应位置。如果你对这部分内容感到不理解,可以参考下面的说明图。颜色一样代表他们相对应。
代码:
#include<bits/stdc++.h>
using namespace std;
struct node {
int step = 0, dir = -1;
vector<vector<int>> state;
node() {}
node(vector<vector<int>> _state, int _step, int _dir) {
state = _state;
step = _step;
dir = _dir;
}
};
int h, w;
//下面这句话的含义为:创建一个h行w列,名为original的二维动态数组
vector<vector<int>> original(8, vector<int>(8));
vector<vector<int>> target(8, vector<int>(8));
int d[4][2] = {{0, 0},
{0, 1},
{1, 0},
{1, 1}}; //代表旋转区域的左上角位置
map<vector<vector<int>>, int> res[2];
vector<vector<int>> rotate(vector<vector<int>> before, int flag) {
//根据数量关系进行旋转
vector<vector<int>> after(h, vector<int>(w));
int x = d[flag][0], y = d[flag][1];
int sum_i = x * 2 + h - 2, sum_j = y * 2 + w - 2;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
if (i >= x && i <= x + h - 2 && j >= y && j <= y + w - 2) {
//进行旋转
after[i][j] = before[sum_i - i][sum_j - j];
} else after[i][j] = before[i][j]; //其他位置不变
}
}
return after;
}
void bfs(vector<vector<int>> st, int rotate_target_flag) {
queue<node> Q;
Q.push(node(st, 0, -1));
res[rotate_target_flag][st] = 0;
vector<vector<int>> cur;
while (!Q.empty()) {
node cur = Q.front();
Q.pop();
for (int i = 0; i < 4; i++) {
if (i == cur.dir)continue;
vector<vector<int>> rotate_res = rotate(cur.state, i);
if (!res[rotate_target_flag].count(rotate_res)) {
//注意步骤限制,防止超时
if (cur.step < 10)Q.push(node(rotate_res, cur.step + 1, i));
res[rotate_target_flag][rotate_res] = cur.step + 1;
}
}
}
}
int main() {
cin >> h >> w;
for (int i = 0; i < h; i++) {
//此处为了简化,我们将下标换成从0开始
for (int j = 0; j < w; j++) {
cin >> original[i][j];
target[i][j] = i * w + j + 1;
}
}
bfs(original, 0);
bfs(target, 1);
int ans = 21;
for (auto cur: res[0]) {
vector<vector<int>> state = cur.first;
if (res[1].count(state)) {
ans = min(ans, res[0][state] + res[1][state]);
}
}
if (ans > 20)cout << -1 << endl;
else cout << ans << endl;
return 0;
}
学习交流
以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。