ABC394
说个事,为什么 393 没有出笔记?因为觉得太简单了。这次又是 ABCD,非常好啊。
C
这道题大家应该都知道,一个形如 "WWW...WA" 的子串都可以替换成 "ACCC...C",这是一种连通块的思想。我们在序列里面,只处理每个连通块,便可以联想出很多其他的性质,这其实也是一种化困难为简单的思想。
当然,这道题还可以倒着做,每次把 "WA" 换成 "AC",因为 "AC" 的 "A" 是在前面,后面有 "C" 挡住,因此这个新出现的 "A" 就只能与前面的 "W" 进行结合,这样就避免了后效性。
#include <iostream>
#include <algorithm>
#include <time.h>
#include <cstring>using namespace std;typedef long long LL;
typedef pair<int, int> PII;const int N = 1;string a;int main()
{cin >> a;for (int i = 0; i < a.size(); i ++ ){if (a[i] != 'W') continue;int j = i;while (j < a.size() && a[j] == 'W') j ++ ;if (j >= a.size() || a[j] != 'A'){i = j - 1;continue;}for (int k = i + 1; k <= j; k ++ ) a[k] = 'C';a[i] = 'A';i = j;}cout << a << endl;cerr << clock() << endl;return 0;
}
E
给定一个很小的图,边值为一个小写字母,求所有从 \(i\) 到 \(j\) 的路径中边值组成的字符串中是回文且长度最小的那个。
这道题竟然没有给形式化表达。这道题乍一看好像是 Floyd,这个数据范围和图存储方式都在提醒我们呢。可是,我们并不能从 Floyd 当中得到所有回文子串,也不能得到所有方案。因此,这条路是行不通的。
我们发现,回文串除了能用完全相反对称的字符串拼起来以外,还可以从端点加入两个相等的字符得到新的回文串。由于是在图上,每次扩展得到最短回文串,这不就应该使用 BFS 吗?具体来说,我们把有序二元组 \((i, j), \forall 1 \le i,j \le n\) 看成一个点,如果 \((i, j)\) 中间的路径是回文串的话,那么 \(\forall 1 \le k, l \le n, C_{k, i} = C_{j, l} \ 且\ C_{k, i} \not = '-'\),就都有回文路径。这是 BFS 的原理。
注意考虑初始队列,应该加入所有形如 \((i, i)\) 的二元组,并把它的 BFS 的距离数组值设为 \(0\),这个主要是为了方便处理偶数长度的回文串;对于奇数长度,我们把所有 \(i, j\) 间有直接边的二元组加入,并把距离数组值设为 \(1\)。更新的时候,找到两头能走的边当中相等边值的,更新新的状态即可。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <time.h>
#define x first
#define y second using namespace std;typedef long long LL;
typedef pair<int, int> PII;const int N = 110, INF = 0x3f3f3f3f;int n;
char g[N][N];
int dist[N][N];void bfs()
{queue<PII> q;memset(dist, -1, sizeof dist);for (int i = 1; i <= n; i ++ ) q.push({i, i}), dist[i][i] = 0;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )if (i != j && g[i][j] != '-')dist[i][j] = 1, q.push({i, j});while (q.size()){PII t = q.front(); q.pop();int u = t.first, v = t.second;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= n; j ++ )if (dist[i][j] == -1 && g[i][u] == g[v][j] && g[i][u] != '-'){dist[i][j] = dist[u][v] + 2;q.push({i, j});}}
}int main()
{// freopen("contest.in", "r", stdin);// freopen("contest.out", "w", stdout);scanf("%d", &n);for (int i = 1; i <= n; i ++ )scanf("%s", g[i] + 1);bfs();for (int i = 1; i <= n; i ++ ){for (int j = 1; j <= n; j ++ )printf("%d ", dist[i][j]);puts("");}cerr << clock() << endl;return 0;
}
F
这道题应该比 E 简单一些,更好想(当然也别像我一样死磕)。这种树的题目求最大最小,一定要想到树形 DP。这些树应该会自带性质。我们发现,这个树应该除了叶子节点和根节点,都必须度数为 \(4\),即必须三叉。根节点有两种情况,要么出现四叉,要么度数为 \(1\)。我们单开一个状态来表示度数为 \(1\) 的情况的求值。
我们用 \(f_{i, 0}\) 表示以 \(i\) 根且为三叉树的树中最大的点数,\(f_{i, 1}\) 表示以 \(i\) 为根的 Alkane 中最大的点数。三叉树和四叉树要想最大,子树就必须最大。则我们处理出 \(f_{j, 0}\) 前 \(4\) 大值,记为 \(d_i, 1 \le i \le 4\)。转移方程如下:
\(f_{i, 0} = \begin{cases} d_1 + d_2 + d_3 + 1, & 如果有三个或以上的子节点数 \\ 1, & 如果没有三个子节点 \end{cases}\)
\(f_{i, 1} = \begin{cases} d_1 + d_2 + d_3 + d_4 + 1, & 有四个或以上的节点数 \\ d_1 + 1, & 有子节点,且 d_1 \not= 0 \\ 1, & 无子节点 \end{cases}\)
\(f_{i, 1}\) 其实并不需要求出来,直接在 DFS 的过程中统计即可。但是注意我这个代码,对于 \(d_1 + 1\) 这个式子,如果 \(i\) 就是根节点,是不能够更新的。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <time.h>
#define x first
#define y second using namespace std;typedef long long LL;
typedef pair<int, int> PII;const int N = 2e5 + 10, M = N * 2, INF = 0x3f3f3f3f;int n;
int h[N], ne[M], e[M], idx;
int f[N];
int ans;void add(int a, int b)
{ne[++ idx] = h[a], e[idx] = b, h[a] = idx;
}int dfs(int u, int fa)
{int d[4] = {0}, cnt = 0;for (int i = h[u]; i; i = ne[i]){int j = e[i];if (j == fa) continue;int t = dfs(j, u);cnt ++ ;for (int i = 0; i < 4; i ++ )if (t > d[i]){for (int j = 3; j > i; j -- ) d[j] = d[j - 1];d[i] = t;break;}}if (cnt <= 2) return 1; //叶子节点if (u != 1) ans = max(ans, d[0] + d[1] + d[2] + 2);if (cnt >= 4) ans = max(ans, d[0] + d[1] + d[2] + d[3] + 1);return d[0] + d[1] + d[2] + 1;
}int main()
{// freopen("contest.in", "r", stdin);// freopen("contest.out", "w", stdout);scanf("%d", &n);for (int i = 0; i < n - 1; i ++ ){int a, b;scanf("%d%d", &a, &b);add(a, b), add(b, a);}dfs(1, -1);if (!ans) puts("-1");else printf("%d\n", ans);cerr << clock() << endl;return 0;
}