AtCoder Beginner Contest 054
A
题意:
置换群
\(Alice\) 和 \(Bob\) 给出 \(a_A, a_B\) ,询问谁的 \(p_{a_A}, p_{a_B}\) 的大。
相同则输出 Draw 。
std::vector<int> p(14), invp(14);
for (int i = 1; i <= 13; i++) p[i] = i;
for (int i = 1; i <= 13; i++) std::swap(p[1], p[i]);
for (int i = 1; i <= 13; i++) invp[p[i]] = i;
int A, B; std::cin >> A >> B;
std::cout << (A == B ? "Draw" : p[A] < p[B] ? "Bob" : "Alice") << "\n";
B
题意:
给一个 \(N \times N\) 的网格图和一个 \(M \times M\) 的网格图 \(B\) 。询问 \(B\) 是否是 \(A\) 的子图。
\(1 \leq A, B \leq 50\) 。
题解:
暴力是在 \(A\) 内 \(O(N^{2})\) 枚举一个左上角,然后再 \(O(N^{2})\) 改子图是否和 \(B\) 相等。
int N, M; std::cin >> N >> M;
std::vector<std::vector<char> > A(N + 1, std::vector<char>(N + 1));
std::vector<std::vector<char> > B(M + 1, std::vector<char>(M + 1));
for (int i = 1; i <= N; i++) {
std::string s; std::cin >> s;
for (int j = 1; j <= N; j++) {
A[i][j] = s[j - 1];
}
}
for (int i = 1; i <= M; i++) {
std::string s; std::cin >> s;
for (int j = 1; j <= M; j++) {
B[i][j] = s[j - 1];
}
}
int ans = 0;
for (int i = 1; i + M - 1 <= N; i++) {
for (int j = 1; j + M - 1 <= N; j++) {
int ok = 1;
for (int k = i; k <= i + M - 1; k++) {
for (int l = j; l <= j + M - 1; l++) {
ok &= A[k][l] == B[k - i + 1][l - j + 1];
}
}
ans |= ok;
}
}
std::cout << (ans ? "Yes" : "No") << "\n";
依旧考虑枚举 check ,枚举左上角的复杂度是严格 \(O(N M)\) 。按行哈希后, check 可以优化成 \(O(min(N, M))\) 。总复杂度会是 \(O(N M min(N, M))\) 。
能不能更快?可以二维哈希。时间复杂度仅有预处理和枚举左上角的 \(O(N M)\) 。
二维哈希故名思意是二维复合到一维上的 hash 。但是最好只写单哈希,最多调一调 “基” 和 “模数”,否则几乎竞赛现场实现不了。
设第一维的基为 \(B\) ,第二维的基为 \(D\) 。为了方便,这里 \(\bmod 2^{64}\) (比赛现场最好不要)。
将每一行 hash 。
在第一维上复合一次哈希(所以为什么用单哈希。多哈希复合多哈希?想想就痛苦)
单独一维容斥是:
二维容斥?注意第二维是后复合上去,所以先拿出来:
const int N = 2E5+1;
const int B = 257, D = 527;
int pwb[N], pwd[N];
void solve() {
pwb[0] = pwd[0] = 1;
for (int i = 1; i < N; i++) pwb[i] = pwb[i - 1] * B, pwd[i] = pwd[i - 1] * D;
int n, m; std::cin >> n >> m;
std::vector<std::vector<u64> > ha(n + 1, std::vector<u64>(n + 1));
for (int i = 1; i <= n; i++) {
std::string s; std::cin >> s;
for (int j = 1; j <= n; j++) {
ha[i][j] = ha[i][j - 1] * B + s[j - 1];
}
}
std::vector<std::vector<u64> > ga(n + 1, std::vector<u64>(n + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
ga[i][j] = ga[i - 1][j] * D + ha[i][j];
}
}
std::vector<std::vector<u64> > hb(m + 1, std::vector<u64>(m + 1));
for (int i = 1; i <= m; i++) {
std::string s; std::cin >> s;
for (int j = 1; j <= m; j++) {
hb[i][j] = hb[i][j - 1] * B + s[j - 1];
}
}
std::vector<std::vector<u64> > gb(n + 1, std::vector<u64>(n + 1));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= m; j++) {
gb[i][j] = gb[i - 1][j] * D + hb[i][j];
}
}
auto geta = [&] (int u, int d, int l, int r) -> u64 {
return (ga[d][r] - ga[d][l - 1] * pwb[r - l + 1])
- (ga[u - 1][r] - ga[u - 1][l - 1] * pwb[r - l + 1]) * pwd[d - u + 1];
};
auto getb = [&] (int u, int d, int l, int r) -> u64 {
return (gb[d][r] - gb[d][l - 1] * pwb[r - l + 1])
- (gb[u - 1][r] - gb[u - 1][l - 1] * pwb[r - l + 1]) * pwd[d - u + 1];
};
int ok = 0;
for (int i = 1; i + m - 1 <= n; i++) {
for (int j = 1; j + m - 1 <= n; j++) {
if (getb(1, m, 1, m) == geta(i, i + m - 1, j, j + m - 1)) {
ok |= 1;
}
}
}
std::cout << (ok ? "Yes" : "No") << "\n";
}
板子题是
Matrix Matcher
https://www.luogu.com.cn/problem/UVA11019
const int N = 2E5+1;
const int B = 257, D = 527;
int pwb[N], pwd[N];
void solve() {
pwb[0] = pwd[0] = 1;
for (int i = 1; i < N; i++) pwb[i] = pwb[i - 1] * B, pwd[i] = pwd[i - 1] * D;
int n, m; std::cin >> n >> m;
std::vector<std::vector<u64> > ha(n + 1, std::vector<u64>(m + 1));
for (int i = 1; i <= n; i++) {
std::string s; std::cin >> s;
for (int j = 1; j <= m; j++) {
ha[i][j] = ha[i][j - 1] * B + s[j - 1];
}
}
std::vector<std::vector<u64> > ga(n + 1, std::vector<u64>(m + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
ga[i][j] = ga[i - 1][j] * D + ha[i][j];
}
}
int h, w; std::cin >> h >> w;
std::vector<std::vector<u64> > hb(h + 1, std::vector<u64>(w + 1));
for (int i = 1; i <= h; i++) {
std::string s; std::cin >> s;
for (int j = 1; j <= w; j++) {
hb[i][j] = hb[i][j - 1] * B + s[j - 1];
}
}
std::vector<std::vector<u64> > gb(h + 1, std::vector<u64>(w + 1));
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
gb[i][j] = gb[i - 1][j] * D + hb[i][j];
}
}
auto geta = [&] (int u, int d, int l, int r) -> u64 {
return (ga[d][r] - ga[d][l - 1] * pwb[r - l + 1])
- (ga[u - 1][r] - ga[u - 1][l - 1] * pwb[r - l + 1]) * pwd[d - u + 1];
};
auto getb = [&] (int u, int d, int l, int r) -> u64 {
return (gb[d][r] - gb[d][l - 1] * pwb[r - l + 1])
- (gb[u - 1][r] - gb[u - 1][l - 1] * pwb[r - l + 1]) * pwd[d - u + 1];
};
int ok = 0;
for (int i = 1; i + h - 1 <= n; i++) {
for (int j = 1; j + w - 1 <= n; j++) {
if (getb(1, h, 1, w) == geta(i, i + h - 1, j, j + w - 1)) {
ok += 1;
}
}
}
// std::cout << (ok ? "Yes" : "No") << "\n";
std::cout << ok << "\n";
}
C
题意:
给一个没有自环和重边的 \(N\) 个点 \(M\) 条边的无向无权图。
第 \(i(1 \leq i \leq M)\) 条边连接点 \(a_i, b_i\) 。
询问有多少条路径从 \(1\) 出发,且最终恰好访问 \(N\) 个节点。
\(2 \leq n \leq 8, 0 \leq M \leq \frac{N(N - 1)}{2}, 1 \leq a_i, b_i \leq N\)
题意:
对访问过的点标记,dfs 一定能遍历所有路径,统计满足条件的路径。
时间复杂度是 \(O(N!)\) 。
能不能更快?应该不是很能。
D
题意:
海豚准备制造一些化学物质 \(C\) 。\(C\) 由物质 \(A\) 和物质 \(B\) 以 \(M_a : M_b\) 的比率混合而成。
药店中销售 \(N\) 种药物,每种药物只有一包库存。第 \(i\) 种药物包含 \(a_i\) 克 \(A\) 和 \(b_i\) 克 \(B\) ,售价为 \(c_i\) 。
海豚可以在药典买一些药物,但所有买了的药物的物质 \(A\) 和物质 \(B\) 必须全部用于制造物质 \(C\) 。
询问生成物质 \(C\) 最少需要支付的代价。或表述这是不可能的。
\(1 \leq N \leq 40, 1 \leq a_i, b_i \leq 10, 1 \leq c_i \leq 100, 1 \leq M_a, M_ \leq 10, gcd(M_a, M_b) = 1\)
题解:
最简单的切入点是,可以先尝试处理出最终贡献:考虑了所有药物(不一定买),获得 \(i\) 份 \(A\) 和 \(j\) 份 \(B\) 最少需要的价钱。假设是 \(f_{i, j}\)
然后在最终贡献里找答案,查询 \(f_{k \times M_a, k \times M_b}\) 。
怎么得到 \(f\) ?经典模型:从一堆元素中选出某些元素,可能会附加一些状态。
那么大概率由小集合往大集合 DP 就行,然后分析是否真的能 DP 。
考虑 \(f_{i, j, k}\) 为:考虑了前 \(i\) 种药物,能得到 \(j\) 份 \(A\) 和 \(k\) 份 \(B\) 需要的最少消费。(不能到达的状态需要正无穷消费)
怎么 \(DP\) ?其实这时候已经是个非常典型的 DP 公式了。
答案就是枚举 \(c\) ,检查 \(f_{n, c \times M_a, c \times M_b}\) 。
DP 只是工具,重要的是 DP 之前,能明白问题切入点。
浙公网安备 33010602011771号