abc 394 题解
ABCD
略
E
题目大意
给定一张图,对于图上的每两个点问这两点之间存不存在回文路径, 存在的话长度最短是多少
解题思路
(感觉难度 E > F)
首先在空路径的两端不断添加相同的路径,我们可以从短到长地找到所有长度为偶数的回文路径;而在长度为 1 的路径两端不断添加相同的路径,我们可以从短到长地找到所有长度为奇数的回文路径。
CODE
void solve()
{
int n = 0;
std::cin >> n;
std::vector g(n, std::string{});
for (auto &s : g) {
std::cin >> s;
}
std::vector dis(n, std::vector(n, -1));
std::queue<std::array<int, 2>> q;
for (int i = 0; i < n; i++) {
dis[i][i] = 0;
// 空白路径 从此拼出长度为偶数的回文路径
q.push({ i, i });
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j && g[i][j] != '-') {
dis[i][j] = 1;
// 长度为 1 的路径 从此拼出长度为奇数的回文路径
q.push({ i, j });
}
}
}
// 从已有的回文串向两边拓展
while (not q.empty()) {
auto [x, y] = q.front();
q.pop();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dis[i][j] == -1 && g[i][x] == g[y][j] && g[i][x] != '-') {
dis[i][j] = dis[x][y] + 2;
q.push({ i, j });
}
}
}
}
for (auto &v : dis) {
for (auto &i : v) {
std::cout << i << ' ';
}
std::cout << '\n';
}
return;
}
F
题目大意
定义 alkane 树为满足以下性质的树:
- 树中每个节点的度都是 1 或 4
- 至少有一个节点的度是 4
给定一颗树,问这颗树是否存在一个子图构成 alkane 树,存在的话,这个子图中最多有多少个节点。
解题思路
我们定义合法子图是满足以下条件之一的子图:
- 子图中只有一个节点
- 子图是由一个节点连接三个合法子图得到的
对以上做出定义后,我们就可以这样得到一颗 alkane 树:
- 一个节点任意连接四个合法子图得到一颗 alkane 树
- 一个节点连接任意一个大小不为 1 的合法子图得到一颗 alkane 树
所以我们就可以直接树上 dp 了。具体直接看 dfs 就好了。
CODE
constexpr int N = 2e5, M = 1e5, Inf = 1e9;
std::vector<int> g[N + 5];
// f[i][0] : 以 i 为根节点的 alkane 树的最大大小
// f[i][1] : 以 i 为根节点的合法子图的最大大小
std::vector f(N + 5, std::array<int, 2>{ -1, 1 });
void dfs(int cur, int fa = 0) {
// 用于维护子节点中前 4 大的合法子图
std::priority_queue<int> q;
int mx = 0;
for (auto &to : g[cur]) {
if (to == fa) {
continue;
}
dfs(to, cur);
// 取反相当于是大根堆当小根堆用
q.push(-f[to][1]);
mx = std::max(mx, f[to][1]);
}
// 一个连接大小不为 1 的合法子图得到一颗 alkane 树
if (mx > 1) {
f[cur][0] = mx + 1;
}
// 只保留前 4 大的
while (q.size() > 4) {
q.pop();
}
int mn = 0;
if (q.size() == 4) {
mn = -q.top();
q.pop();
}
if (q.size() == 3) {
int sum = 0;
while (not q.empty()) {
sum -= q.top();
q.pop();
}
// 合法子图的大小
f[cur][1] = sum + 1;
// 存在第 4 个合法子图时才能构成 alkane 树
if (mn > 0) {
f[cur][0] = sum + mn + 1;
}
}
}
void solve()
{
int n = 0;
std::cin >> n;
for (int i = 0; i < n - 1; i++) {
int u = 0, v = 0;
std::cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1);
int ans = -1;
for (int i = 1; i <= n; i++) {
ans = std::max(ans, f[i][0]);
}
std::cout << ans << '\n';
}
G
题目大意
有一个 \(H \times W\) 的矩阵,格子 \((i, j)\) 中都有一栋最高层数为 \(F_{i, j}\) 的建筑。在同一栋建筑中我们可以上下;在相邻的建筑(共边)之间我们可以平移(即所处层数不变),但是要求平移的终点在不超过目标建筑的顶层。
回答若干询问,每次询问给出起点和终点的坐标和层数,问从起点到终点至少要上下多少层楼。
解题思路
首先从起点是一定可以到达终点的,因为一开始我们就可以降到 1 层,然后可以通过平移到达任何地方,到达终点后再上升就好了。
显然这样不一定是最优的,我们没有必要降到一层,我们只需要降到 \(M\) 层,\(M\) 是从起点到终点所经过的建筑中,层数最低的那栋的高度。于是对于每个询问只要找到了最大的 \(M\),我们就解决了问题。
视相邻的建筑之间有一条边,边权是两栋建筑层数的最小值,于是问题又变成了在两个点间找到一条路径,使这条路径中最小的边权最大。
我的做法是先按 kruskal 最小生成树的算法找到图中的最大生成树。然后对于每个询问,通过树上 st 表找两点的 lca、找生成树上两点间的最小边。(实际上用 kruskal 重构树就只用找 lca,但一开始没想到)。
CODE(生成树)
constexpr int N = 500 * 500, M = 1e6, Inf = 1e9, L = 16;
int fa[N];
int get(int u) {
return u == fa[u] ? u : (fa[u] = get(fa[u]));
}
bool merge(int u, int v) {
int fu = get(u), fv = get(v);
fa[fv] = fu;
return fu != fv;
}
std::vector<std::array<int, 2>> g[N];
std::vector st(N, std::vector(L, std::array<int, 2>{}));
std::vector deep(N, 0);
void dfs(int cur) {
for (int l = 1; (1 << l) <= deep[cur]; l++) {
st[cur][l][0] = st[st[cur][l - 1][0]][l - 1][0];
st[cur][l][1] = std::min(st[cur][l - 1][1], st[st[cur][l - 1][0]][l - 1][1]);
}
for (auto &[to, val] : g[cur]) {
if (to == st[cur][0][0]) {
continue;
}
st[to][0] = { cur, val };
deep[to] = deep[cur] + 1;
dfs(to);
}
}
int LCA(int u, int v) {
if (deep[u] != deep[v]) {
if (deep[u] > deep[v]) {
std::swap(u, v);
}
for (int i = L; i >= 0; i--) {
if (deep[v] - (1 << i) >= deep[u]) {
v = st[v][i][0];
}
}
}
if (u == v) {
return u;
}
for (int i = L; i >= 0; i--) {
if ((1 << i) <= deep[u] && st[u][i][0] != st[v][i][0]) {
u = st[u][i][0];
v = st[v][i][0];
}
}
return st[u][0][0];
}
int minval(int u, int v) {
if (deep[u] > deep[v]) {
std::swap(u, v);
}
int res = Inf;
for (int d = deep[v] - deep[u], l = 0; d; d >>= 1, l++) {
if (d & 1) {
res = std::min(res, st[v][l][1]);
v = st[v][l][0];
}
}
return res;
}
void solve()
{
int h = 0, w = 0;
std::cin >> h >> w;
std::vector f(h, std::vector(w, 0));
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
std::cin >> f[i][j];
}
}
// 分离边
std::vector<std::array<int, 3>> edge;
for (int i = 0; i < h; i++) {
for (int j = 1; j < w; j++) {
int u = i * w + j;
edge.push_back({ u - 1, u, std::min( f[i][j], f[i][j - 1]) });
}
}
for (int j = 0; j < w; j++) {
for (int i = 1; i < h; i++) {
int u = i * w + j;
edge.push_back({ u - w, u, std::min( f[i][j], f[i - 1][j]) });
}
}
// 生成树的边
std::sort(edge.begin(), edge.end(), [](auto &u, auto &v) {
return u[2] > v[2];
});
for (int i = 0; i < h * w; i++) {
fa[i] = i;
}
for (auto &[u, v, val] : edge) {
if (merge(u, v)) {
g[u].push_back({ v, val });
g[v].push_back({ u, val });
}
}
// 生成树上维护 lca 和 st 表
dfs(0);
int q = 0;
std::cin >> q;
while (q--) {
int a = 0, b = 0, x = 0;
int c = 0, d = 0, z = 0;
std::cin >> a >> b >> x >> c >> d >> z;
a--, b--, c--, d--;
int u = a * w + b, v = c * w + d;
int lca = LCA(u, v);
std::cout << x + z - 2 * std::min({ x, z, minval(u, lca), minval(v, lca) }) << '\n';
}
return;
}
浙公网安备 33010602011771号