AtCoder Beginner Contest 336题解
1.F
题目的解法主要是基于双向BFS对时间复制度的优化。
在20移动的基础上如果采用bfs,时间复杂度是,这种时间复制度显然是无法接受的。所以就采用双向BFS,一个从起点状态开始搜索,一个从终点状态开始搜索,双方各搜索十次。如何判断有解呢?这个看两者搜索是否会产生状态重叠。如果产生了状态重叠证明是可以在20步以内到达的。
using i64 = long long;
struct Node {
vector<vector<int>> a;
int cnt, op;
};
int n, m;
map<vector<vector<int>>, int> mp;
// 旋转x, y为左上角的矩阵180
vector<vector<int>> rotate(vector<vector<int>> a, int x, int y) {
vector<vector<int>> b = a;
for (int i = 1; i <= n - 1; i++) {
for (int j = 1; j <= m - 1; j++) {
b[i + x][j + y] = a[n - i + x][m - j + y];
}
}
return b;
}
void print(vector<vector<int>> a) {
cout << "------------------\n";
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
cout << a[i][j] << " ";
cout << "\n";
}
cout << "------------------\n";
}
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
vector<vector<int>> a(n + 1, vector<int>(m + 1)), b(n + 1, vector<int>(m + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
b[i][j] = i * m - m + j;
}
}
// 先从初始状态开始搜索
queue<Node> q;
q.push({a, mp[a] = 0, -1});
while (!q.empty()) {
auto [now, cnt, op] = q.front();
q.pop();
if (cnt == 10)
continue;
for (int i = 0; i < 4; i++) {
auto nowChange = rotate(now, i / 2, i % 2);
if (mp.find(nowChange) == mp.end()) {
mp[nowChange] = cnt + 1;
q.push({nowChange, cnt + 1, i});
}
}
}
//再从结尾状态开始搜索。
queue<Node> q1;
q1.push({b, 0, -1});
while (!q1.empty()) {
auto [now, cnt, op] = q1.front();
q1.pop();
if (mp.find(now) != mp.end()) {
cout << cnt + mp[now];
return 0;
}
if (cnt == 10)
continue;
for (int i = 0; i < 4; i++) {
auto nowChange = rotate(now, i / 2, i % 2);
q1.push({nowChange, cnt + 1, i});
}
}
cout << -1;
return 0;
}
2.E
统计数字状态通过数位dp来解决问题。
简意大概就是小于N 的数字中多少数字满足数字大小可以整除数位和。
基于数位dp,设dp[i][j][k]表示从高位算起,第i位数字,当前数字和为j, 取模结果为k的数字有多少。
这样就只需要枚举取模的数字大小,取模的数字大小最多为 位数*9。然后考虑枚举数位和。找到取模当前数位和为0的数字个数,最后累加即可。
具体代码比较重要的两处有注解。
using i64 = long long;
i64 n, m, cnt = 0;
i64 a[20], dp[20][10 * 20][10 * 20];
i64 dfs(i64 pos, i64 sum, i64 mod, i64 flag) {
if (sum > m)
return 0;
if (pos == 0)
return sum == m && mod == 0;
if (!flag && dp[pos][sum][mod] != -1)
return dp[pos][sum][mod];
i64 x = flag ? a[pos] : 9;
i64 ans = 0;
for (i64 i = 0; i <= x; i++) {
// 这个取模的拿个数字举个例子就明白了,比如说23 % 6 => 2 % 6 = 2 => (2 * 10 + 3) % 6 = 5
ans += dfs(pos - 1, sum + i, (mod * 10 + i) % m, flag && (i == a[pos]));
}
if (!flag)
dp[pos][sum][mod] = ans;
return ans;
}
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
while (n) {
a[++cnt] = n % 10;
n /= 10;
}
i64 res = 0;
for (int i = 1; i <= 9 * cnt; i++) { // m考虑的是数位和的大小,最大为9 * 14
m = i;
memset(dp, -1, sizeof(dp));
res += dfs(cnt, 0, 0, 1);
}
cout << res;
return 0;
}
3.D
线性dp。考虑每个位置往左往右的最大长度,然后只能取其中最小的作为这个的点的金字塔长度。
两个dp数组,ldp, rdp,然后具体转移看代码的转移柿子。
const int N = 2e5 + 10;
int a[N], ldp[N], rdp[N];
signed main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) {
if (a[i] > a[i - 1])
ldp[i] = ldp[i - 1] + 1;
else
ldp[i] = min({a[i], i, ldp[i - 1] + 1});
}
for (int i = n; i >= 1; i--) {
if (a[i] > a[i + 1])
rdp[i] = rdp[i + 1] + 1;
else
rdp[i] = min({a[i], n - i + 1, rdp[i + 1] + 1});
}
int ans = 0;
for (int i = 1; i <= n; i++) {
// cout << ldp[i] << ' ' << rdp[i] << '\n';
ans = max(ans, min(ldp[i], rdp[i]));
}
cout << ans << '\n';
return 0;
}
4.C
c是一个进制转换题目。
五进制的题,从0开始(题目0算第一个数),所以需要-1。
signed main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
n--;
if (n == 0) {
cout << 0;
return 0;
}
vector<int> ans;
while (n) {
int tmp = n % 5;
ans.push_back(tmp * 2);
n /= 5;
}
for (int i = ans.size() - 1; i >= 0; i--) {
cout << ans[i];
}
return 0;
}
5.B
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<int> a;
while (n) {
int t = n % 2;
a.push_back(n % 2);
n /= 2;
}
int sum = 0;
for (auto i : a) {
if (i == 0)
sum++;
else
break;
}
cout << sum << '\n';
return 0;
}
6.A
int main() {
std::ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
cout << "L";
for (int i = 0; i < n; i++) {
cout << 'o';
}
cout << "n";
cout << "g";
return 0;
}