230513 第二次周考
第一次周考被菌哥哥(白眼)吃了
A. 行走的机器人
http://222.180.160.110:1024/contest/3583/problem/1
不难想到用 std::next_permutation
解决方向问题,然后模拟走路过程判定。
复杂度 \(\mathcal O(24n^2)\),其中 \(24=A_4^4\)。赛时耗时 14min。
namespace XSC062 {
using namespace fastIO;
const int maxn = 55;
const int maxm = 105;
const int fx[] = { 1, -1, 0, 0 };
const int fy[] = { 0, 0, 1, -1 };
int a[maxn];
char s[maxm];
char t[maxn][maxn];
int n, m, l, x, y, sx, sy, ex, ey, res;
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%s", t[i] + 1);
for (int j = 1; j <= m; ++j) {
if (t[i][j] == 'S')
sx = i, sy = j;
if (t[i][j] == 'E')
ex = i, ey = j;
}
}
scanf("%s", s + 1);
l = strlen(s + 1);
for (int i = 0; i < 4; ++i)
a[i] = i;
for (int i = 1; i <= 24; ++i) {
x = sx, y = sy;
for (int j = 1; j <= l; ++j) {
x += fx[a[s[j] - '0']];
y += fy[a[s[j] - '0']];
if (x < 1 || y < 1 || x > n ||
y > m || t[x][y] == '#')
goto EndLoop;
if (x == ex && y == ey) {
++res;
goto EndLoop;
}
}
EndLoop: ;
std::next_permutation(a, a + 4);
}
printf("%d", res);
return 0;
}
} // namespace XSC062
B. 多米诺骨牌涂色
http://222.180.160.110:1024/contest/3583/problem/2
很明显的一道小 DP。
我一开始定义的状态是 \(f_{i, j, k}\) 表示在第 \((i, j)\) 格涂 \(k\) 颜色的方案数。
但状态越多,求解会出现的 bug 就越多。我在无数次调试后发现颜色其实是可轮换的,我们只需要将状态简化为 \(f_{i, j}\) 表示 \((i, j)\) 格确定 某种颜色。所以转移时不用讨论上一列的排列(即认定上一列为某种颜色,不作枚举),只讨论当前列的排列。
首先明白一点:任意两块横向长方形不可能构成 Z 字形的摆放。这一点很好证明,我们后面的分类讨论和状态转移也会因为这个性质简单很多。
对于从第二列开始的任意一列,该列内的情况只有可能是:
-
该列为一块竖向排列的长方形
-
上一列也为一块竖向排列的长方形
假设上一列的颜色是 \(a\),那么这一列只能是 \(b\) 或 \(c\)。
故 \(f_{1, i}, f_{2, i} = 2\times f_{1, i - 1}\)。
因为 \(f_{1, i - 1}\) 和 \(f_{2, i - 1}\) 也是轮换的(因为不存在 Z 形),所以直接用 \(f_{1, i - 1}\) 代替即可。
-
上一列是两块横向排列的长方形的结尾
假设上一列的两种颜色从上到下为 \((a, b)\),那么这一列只能是 \(c\)。
故 \(f_{1, i}, f_{2, i} = f_{1, i - 1}\)。
-
-
该列为两块横向排列的长方形
-
该列为结尾的两块
继承开头的状态即可。即 \(f_{1, i} = f_{1, i - 1},f_{2, i} = f_{2, i - 1}\)。
-
该列为开头的两块
-
上一列为一块竖向长方形
假设上一列颜色为 \(a\),那么这一列上下两块的颜色排列既可以是 \(b, c\),也可以是 \(c, b\)。
故 \(f_{1, i}, f_{2, i} = 2\times f_{1, i - 1}\)。
-
上一列为两块横向长方形的末尾
假设上一列从上到下为 \((a, b)\) 两种颜色,那么这一列从上到下可以是 \((b, a)\),\((b, c)\) 或 \((c, a)\) 三种情况。
故 \(f_{1, i}, f_{2, i} = 3\times f_{1, i - 1}\)。
-
-
最后注意初始化。
-
当第一列为一块竖向长方形时
该列没有任何限制,所以 \(f_{1, 1}, f_{2, 1} = 3\)。
-
当第一列为两块横向长方形的开头时
由排列组合得 \(f_{1, 1}, f_{2, 1} = A_3^2 = 6\)。
时间复杂度 \(\mathcal O(n)\)。
看了题解过后才意识到,原来状态还可以再简化!因为 \(f_{1, i}\) 和 \(f_{2, i}\) 始终是相等的。但是我懒得改了!
赛时耗时 1h8min。
#define int long long
namespace XSC062 {
using namespace fastIO;
const int maxn = 55;
const int mod = 1e9 + 7;
int n;
int f[5][maxn];
char t[5][maxn];
int main() {
scanf("%lld", &n);
for (int i = 1; i <= 2; ++i)
scanf("%s", t[i] + 1);
if (t[2][1] == t[1][1])
f[1][1] = f[2][1] = 3;
else f[1][1] = f[2][1] = 6;
for (int i = 2; i <= n; ++i) {
// 该列为竖向
if (t[1][i] == t[2][i]) {
// 上一列也为竖向
if (t[1][i - 1] == t[2][i - 1]) {
f[1][i] = f[2][i] =
(2 * f[1][i - 1]) % mod;
}
// 上一列为横向
else f[1][i] = f[2][i] = f[1][i - 1];
}
// 该列为横向
else {
// 该列为结尾
if (t[1][i] == t[1][i - 1]) {
f[1][i] = f[1][i - 1];
f[2][i] = f[2][i - 1];
}
// 该列为开头
else {
// 上一列为竖向
if (t[1][i - 1] == t[2][i - 1]) {
f[1][i] = f[2][i] =
(2 * f[1][i - 1]) % mod;
}
// 上一列也为横向
else {
f[1][i] = f[2][i] =
(3 * f[1][i - 1]) % mod;
}
}
}
}
print(f[2][n]);
return 0;
}
} // namespace XSC062
#undef int
C. 最小得分
http://222.180.160.110:1024/contest/3583/problem/3
如果数组里没有 0,皆大欢喜,输出 0。
如果数组里有 0,那就隔一个非零数插一个 0,答案肯定是 0。
如果按上面的方法插完了还有 0,那就说明相邻的 0 是一定会存在的,那么就可以把非 0 数全部堆在一起,那么次小的至少都是 1 + 1 = 2。此时输出最小值和次小值之间的 1 即可。
但有一种例外,那就是整个数组除了 0 就只有 1,那么就说明一定会有一个 1 紧挨着 0,那么我们还是按照隔一个插一个的策略,答案就是 2。
全是 0 也应该输出 1,但是这种情况会被我们之前的讨论涵盖。
namespace XSC062 {
using namespace fastIO;
const int maxn = 2e5 + 5;
int a[maxn];
int T, n, cnt0, mx;
inline int max(int x, int y) {
return x > y ? x : y;
}
int main() {
read(T);
while (T--) {
read(n);
cnt0 = mx = 0;
for (int i = 1; i <= n; ++i) {
read(a[i]), cnt0 += (!a[i]);
mx = max(mx, a[i]);
}
if (cnt0 <= n - cnt0 + 1)
print(0, '\n');
else if (mx == 1)
print(2, '\n');
else print(1, '\n');
}
return 0;
}
} // namespace XSC062
—— · EOF · ——
真的什么也不剩啦 😖