A.Long Loong(格式输出)
题意:
给出一个正整数XXX,请你输出一个'L',然后XXX个'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(二进制)
题意:
给出一个正整数XXX,请你求出XXX在二进制下末尾有多少个连续的0。
分析:
循环除222直到输入的XXX变为奇数为止,循环的次数即为答案。
优化
直接使用函数计算答案,答案为log2lowbit(X)log_2^{lowbit(X)}log2lowbit(X)
Tips:系统封装了库函数log2()log2()log2()可用于计算222为底数的对数,lowbitlowbitlowbit则需自己实现
代码:
#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作为十进制上每一位数字的选择,问能组成的第NNN小的数字是多少(从小到大)。
分析:
不难发现,选择完最高位的数字后,假设后面还有mmm位十进制数字需要选择,此时的方案总数共5k5^{k}5k种。
那么,可以先找到最高位的数字是多少,即先计算出第一个满足(m+1)=5k≥N(m + 1) = 5^{k} \ge N(m+1)=5k≥N的mmm,然后将mmm作为除数依次计算得到每一位的数字即可。
代码:
#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)
题意:
金字塔定义:一个高度为kkk的金字塔序列共包含(2k−1)(2k - 1)(2k−1)个数字,且序列格式为:1,2,...,k−1,k,k−1,...,2,11, 2, ..., k - 1, k, k - 1, ..., 2, 11,2,...,k−1,k,k−1,...,2,1。
给出一个包含nnn个数字的序列A=(A1,A2,...,An)A = (A_1, A_2, ..., A_n)A=(A1,A2,...,An),你可以进行若干次以下操作:
-
选择一个序列中的数字,将该数字减少111
-
移除序列开头或末尾的数字
问,能获得的所有金字塔序列中,高度最高的金字塔的高度是多少?
分析:
将金字塔序列从中心分成两半,使用pre[i],nxt[i]pre[i], nxt[i]pre[i],nxt[i]分别表示以第iii个数字作为金字塔中心,能组成的最长前半和后半序列的长度。
对于以第iii个数字作为金字塔中心,需要考虑两种情况:
-
前/后面序列构造的金字塔高度很小,那么当前中心点再高也只能通过减少高度到前/后面序列的最高高度加一。
-
前/后面序列构造的金字塔高度很大,那么只能通过删除前/后面部分数字,然后将接近中心的部分数字依次减少1,此时的金字塔高度即为当前数字。
依次计算得到pre,nxtpre, nxtpre,nxt数组,再枚举所有点为中心,此时选择第iii个数字为中心,能构造的最高金字塔高度即为min(pre[i],nxt[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)
题意:
对于一个数字xxx,我们定义它各数位上数字的和为sss,如x=2024,s=2+0+2+4=8x=2024, s=2+0+2+4=8x=2024,s=2+0+2+4=8。现在我们想知道1−N1-N1−N之间有多少个数字xxx满足xxx是sss的倍数。
分析:
本题N≤1014N\le10^{14}N≤1014,即我们可以认为s≤14×9=126s\le14\times9=126s≤14×9=126,即:无论如何,sss最多也不会超过126126126,因此,我们可以对满足1≤s≤1261\le s\le1261≤s≤126和x≤Nx\le Nx≤N的数进行枚举,枚举的过程是通过枚举各个数位上的数来实现的。为了便于获取每一位上的数,我们可以将NNN看做一个字符串,N[d]N[d]N[d]代表从高往低第ddd位的值。
对于每一个可能的目标targettargettarget,我们设置dpdpdp数组,dp[d][cur][rmd]dp[d][cur][rmd]dp[d][cur][rmd]表示:现在我们对第ddd位的数进行枚举,之前枚举结果的数位和为curcurcur,除以targettargettarget的余数为rmdrmdrmd的方案数。那么很容易想到:假设第ddd位上我们放置ttt,那么dp[d+1][cur+t][(rmd∗10+t)%target]dp[d+1][cur+t][(rmd*10+t)\%target]dp[d+1][cur+t][(rmd∗10+t)%target]与dp[d][cur][rmd]dp[d][cur][rmd]dp[d][cur][rmd]的值直接相关,需要进行更新。
但是,本题有一个限制:我们构建的数字不能超过NNN,那么当我们构建到第ddd位时,就要根据之前构建的情况进行分类讨论。若我们构建出的数字从第000位到第d−1d-1d−1位都和NNN一模一样(称为flagflagflag条件),那么我们根据枚举对象ttt的大小,进行分类讨论:
| 大小关系 | 代表含义 |
|---|---|
| t<N[d]t < N[d]t<N[d] | 枚举对象没有超过nnn在这一位上的内容,低位可以是任意数字(都不会超过nnn) |
| t=N[d]t = N[d]t=N[d] | 枚举对象到第ddd位仍然保持一致,继续传递下去,向后讨论 |
| t>N[d]t > N[d]t>N[d] | 枚举对象超过了nnn在这一位上的内容,不可能,无需考虑这一情况 |
而若flagflagflag条件不满足,则后面的位置上可以随意枚举任意的数。因此我们增添一个维度flagflagflag,来进行更新。当ddd与NNN的长度一致,且rmd=0rmd=0rmd=0(没有余数,是满足要求的数字),且cur=targetcur=targetcur=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(折半搜索)
题意:
有一个HHH行WWW列的格栅图,对应着数字111到H×WH\times WH×W,即格子(i,j)(i,j)(i,j)对应着1≤Si,j≤H×W1\le S_{i,j}\le H\times W1≤Si,j≤H×W。我们现在希望在若干次操作以后,能够使得Si,j=(i−1)×W+jS_{i,j}=(i-1)\times W+jSi,j=(i−1)×W+j即从左到右、从上到下,依次为1,2,…,H×W1,2,\dots,H\times W1,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
如果你能在202020次操作内将矩阵转换成理想状态,则输出最少的步数,否则输出−1-1−1。
分析:
根据题目所说的旋转区域大小,我们可以认为只存在444种方案,分别对应左上角、左下角、右上角、右下角,对应202020步,就是4×3194\times3^{19}4×319(因为连续旋转同一个区域222次是没有意义的),这个数字超过了4×1094\times10^94×109,仍然会超时。
我们注意到这个过程中指数有很大的影响,此时我们想到可以使用折半搜索:因为我们知道起始状态,也知道目标状态,那么我们可以分别将起始状态向后进行变化,将目标状态向前进行还原,此时对于起始状态,它进行101010次旋转对应的方案数为4×394\times3^94×39,不超过×105\times10^5×105种可能,此时时间复杂度就大大下降了。在枚举过程中,我们使用广度有限搜索,记录步数与对应的旋转结果。
在实现旋转过程时,假设(i,j)(i,j)(i,j)与(k,l)(k,l)(k,l)对应,那么我们有:i+k=2∗x+h−2i+k=2*x+h-2i+k=2∗x+h−2,以及j+l=2∗y+w−2j+l=2*y+w-2j+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,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

1651

被折叠的 条评论
为什么被折叠?



