JOISC 2020 做题记录
JOISC 2020 做题记录
Day1 T1 Building 4
题意
有两个长度为 \(2n\) 的序列 \(a_1, \ldots, a_{2n}\),\(b_1, \ldots, b_{2n}\),构造一个长度为 \(2n\) 的序列 \(c\),要求 \(c\) 每个位置的值为 \(a, b\) 对应位置中选择一个,整体共有 \(n\) 个位置为 \(a\),另外 \(n\) 个位置选了 \(b\),且 \(c\) 单调不减。求一种方案。
\(n \le 5 \times 10 ^ 5\)。
题解
首先有一个非常显然的 \(\mathcal{O}(n ^ 2)\) 的 DP 做法。记 \(f(i, j, 0/1)\) 表示只考虑了前 \(i\) 个位置,选了 \(j\) 个 \(a\) ,第 \(i\) 位选了 \(a\) / \(b\) 的是否可能,直接转移。还原方案路径还原即可。
考虑优化,不难猜到一个结论:对于一个确定的 \(i\),所有使得 \(f(i, j, 0/1)\) 值为真的 \(j\),一定是一段连续的区间。
一种不严谨的证明是,我们考虑更换 DP 的方式。我们定义另一个 DP,\(g(i, 0/1)\) 表示只考虑了前 \(i\) 个位置,第 \(i\) 位选了 \(a\) / \(b\) 的,使得值为真的 \(j\) 的集合。
然后把这个状态转移的显式图给画出来,令 \(A_i\) 表示 \(g(i, 0)\) 对应的点,\(B_i\) 表示 \(g(i, 1)\) 对应的点。能够发现,如果 \(j\) 不是一段连续的区间,则必然需要存在一个位置 \(x\),满足 \(A_x\) 向 \(A_{x + 1}\) 连边(即 \(a_x \le a_{x + 1}\)),\(B_x\) 向 \(B_{x + 1}\) 连边,\(A_x\) 不能向 \(B_{x + 1}\) 连边,\(B_x\) 不能向 \(A_{x + 1}\) 连边。
然而这是不可能的。分类讨论一下 \(a_x\) 和 \(b_x\) 的大小关系。若 \(a_x \le b_x\),则 \(a_x \le b_x \le b_{x + 1}\),说明 \(A_x\) 有向 \(B_{x + 1}\) 连边;若 \(b_x \lt a_x\),则 \(b_x \lt a_x \le a_{x + 1}\),说明 \(B_x\) 有向 \(A_{x + 1}\) 连边,都与条件矛盾。于是结论得证。
知道可行的 \(j\) 是一个区间就简单了,用和 \(g\) 一样的 DP 方法,区间只要存左右端点就好了,就优化掉了一个 \(n\)。
时间复杂度 \(\mathcal{O}(n)\),空间复杂度 \(\mathcal{O}(n)\)。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
const int MaxN = 1000000;
int N;
int A[MaxN + 5], B[MaxN + 5];
int L[MaxN + 5][2], R[MaxN + 5][2];
bool F[MaxN + 5][2];
int Ans[MaxN + 5];
void init() {
scanf("%d", &N);
N <<= 1;
for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
for (int i = 1; i <= N; ++i) scanf("%d", &B[i]);
}
void solve() {
L[0][0] = L[0][1] = R[0][0] = R[0][1] = 0;
F[0][0] = F[0][1] = true;
for (int i = 1; i <= N; ++i) {
L[i][0] = N + 1, R[i][0] = -1;
L[i][1] = N + 1, R[i][1] = -1;
if (A[i] >= A[i - 1] && F[i - 1][0] == true) {
L[i][0] = std::min(L[i][0], L[i - 1][0] + 1);
R[i][0] = std::max(R[i][0], R[i - 1][0] + 1);
F[i][0] = true;
}
if (A[i] >= B[i - 1] && F[i - 1][1] == true) {
L[i][0] = std::min(L[i][0], L[i - 1][1] + 1);
R[i][0] = std::max(R[i][0], R[i - 1][1] + 1);
F[i][0] = true;
}
if (B[i] >= A[i - 1] && F[i - 1][0] == true) {
L[i][1] = std::min(L[i][1], L[i - 1][0]);
R[i][1] = std::max(R[i][1], R[i - 1][0]);
F[i][1] = true;
}
if (B[i] >= B[i - 1] && F[i - 1][1] == true) {
L[i][1] = std::min(L[i][1], L[i - 1][1]);
R[i][1] = std::max(R[i][1], R[i - 1][1]);
F[i][1] = true;
}
}
int ok = -1;
if (F[N][0] == true && L[N][0] <= N / 2 && N / 2 <= R[N][0]) ok = 0;
if (F[N][1] == true && L[N][1] <= N / 2 && N / 2 <= R[N][1]) ok = 1;
if (ok == -1) puts("-1");
else {
int last = N / 2;
for (int i = N; i >= 1; --i) {
Ans[i] = ok;
if (ok == 0) {
last--;
if (F[i - 1][0] == true && A[i - 1] <= A[i] && L[i - 1][0] <= last && last <= R[i - 1][0]) ok = 0;
else ok = 1;
} else {
if (F[i - 1][0] == true && A[i - 1] <= B[i] && L[i - 1][0] <= last && last <= R[i - 1][0]) ok = 0;
else ok = 1;
}
}
for (int i = 1; i <= N; ++i)
putchar("AB"[Ans[i]]);
putchar('\n');
}
}
int main() {
init();
solve();
return 0;
}
Day1 T2 Hamburg Steak
题意
平面上有 \(n\) 个矩形,需要选出 \(k\) 个点,使得每个矩形内至少有一个点。数据保证有解。
\(n \le 2 \times 10 ^ 5\),\(k \le 4\)。
题解
不会,先咕了。
Day1 T3 Sweeping
题意
\(n \times n\) 的平面上,初始有 \(m\) 个点,进行 \(q\) 次操作。操作有以下四种:
- 询问一个点的位置。
- 给出参数 \(l\),把所有 \((x, y)\) 满足 \(x \lt n - l, y \le l\) 的点移到 \((n - l, y)\)。
- 给出参数 \(l\),把所有 \((x, y)\) 满足 \(x \le l, y \lt n - l\) 的点移到 \((x, n - l)\)。
- 加入一个点。
\(n \le 10 ^ 9\),\(m \le 5 \times 10 ^ 5\),\(q \le 10 ^ 6\)。
题解
不会,先咕了。
Day2 T1 Chameleon’s Love
题意
这是一道交互题。
有 \(2n\) 只变色龙,\(n\) 只为雄性,\(n\) 只为雌性。每只变色龙有一个初始颜色 \(c_i\),满足对于雄性和雌性内部来说,\(c_i\) 都是一个 \(1 \sim n\) 的排列。此外,每只变色龙还有喜欢的对象 \(l_i\)。
你可以举办不超过 \(20\ 000\) 次派对。在派对中,对于一名与会变色龙 \(x\),若 \(l_x\) 同样与会,则它的颜色会是 \(c_{l_x}\),否则颜色是 \(c_x\)。你可以得到与会变色龙颜色的种类数。请确定每对颜色相同的变色龙。
\(n \le 500\)。
题解
不会,先咕了。
Day2 T2 Making Friends on Joitter is Fun
题意
给出一张 \(n\) 个点的图,进行 \(m\) 次操作,每次操作加入一条有向边 \((u, v)\)。
然后对图进行维护,若存在三元组 \((x, y, z)\),满足存在有向边 \((x, y), (y, z), (z, y)\),则加入有向边 \((x, z)\)。一直操作直到不能增加新边为止。
求每一步后图上的边数。
\(n \le 10 ^ 5\),\(m \le 3 \times 10 ^ 5\)。
题解
不会,先咕着。
Day2 T3 Ruins 3
题意
有 \(2n\) 座石柱,初始时对于每个 \(x ~ (1 \le x \le n)\),都有两座石柱高度为 \(x\),接着发生了 \(n\) 次地震,第 \(i\) 次地震会使得所有的高度不为 \(0\) 的石柱 \(j\),设其原本高度为 \(h_j\),若满足存在一个 \(k\) 使得 \(h_j = h_k\),石柱 \(j\) 的高度就会减一。
最终只剩下了 \(n\) 座石柱,给出这些石柱在原始序列中对应的下标,求能形成这样的初始方案数。对 \(10 ^ 9 + 7\) 取模。
\(n \le 600\)。
题解
不会,先咕了。
Day3 T1 Constellation 3
题意
有一个 \(n \times n\) 的平面,平面上有若干个格子被染黑,其中第 \(i\) 列被染黑的格子为第 \(1\) 行开始到第 \(a_i\) 行的所有格子。
在没被染黑的格子中,有若干关键格。每个关键格有一个价值 \(c_i\)。
你需要删掉一些关键格,使得平面上不存在一个矩形,使得这个矩形内部没有黑色格子,且有两个以上的关键格。代价为删掉关键和价值之和。求最小代价。
\(n, m \le 2 \times 10 ^ 5\)。
题解
首先把所有的楼房往左右推,只保留其左端右端的墙,即第 \(i\) 列与 \(i + 1\) 列间的墙高为 \(\max(a_i, a_{i + 1})\)。现在变成要求矩形内不能有墙。
把序列抽象成一条长度为 \(n\) 的链,连接 \(i\) 号点和 \(i+1\) 号点的值为之间的墙高,即 \(\max(a_i, a_{i + 1})\)。然后建大根 Kruskal 重构树。
对于每一颗星星,我们找到它最浅的祖先,满足 \(weight_f \lt y\),这就保证了 \(weight_{fa(f)} \ge y\),这个类似 NOI 2018 归程的做法,可以倍增找。
把答案转化成所有星星 - 选中星星的和最大值。
然后设计 \(f_i\) 表示在子树 \(i\) 内,加入了所有权值不超过 \(weight_{fa(i)}\) 的星星(也就是不会与将要合并的区间冲突)的最大值。
在 \(u\) 上做合并的时候,一种是不做特别操作,\(f_u = f_{lson(u)} + f_{rson(u)}\)。
另一种是,可能会有一些星星要在 \(u\) 上加入,也就是前面找到的那个最浅的祖先的用处。
加入一颗星星的时候,把树画出来,会发现它的答案是 这颗星星的 \(c\) 加上 一段从叶子到 \(u\) 的路径上(所有不在路径上的儿子)的 \(f\) 值之和。
这个用树链剖分可以简单维护,就是每个节点存下重儿子的 \(f\) 值,在轻重链切换时单独计算一下轻儿子的值。根据一些性质在重链上记后缀和优化到一个 \(\log\)。
时间复杂度 \(\mathcal{O}(n \log n + m \log n)\),空间复杂度 \(\mathcal{O(n \log n)}\)。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
const int MaxN = 200000, MaxM = 200000;
const int MaxV = 400000, MaxLog = 19;
struct edge_t {
int u, v, w;
edge_t(int _u = 0, int _v = 0, int _w = 0) { u = _u, v = _v, w = _w; }
inline friend bool operator<(const edge_t &a, const edge_t &b) { return a.w < b.w; }
};
struct star_t {
int x, y, c;
star_t(int _x = 0, int _y = 0, int _c = 0) { x = _x, y = _y, c = _c; }
};
int N, M, V;
long long Sum;
int Height[MaxN + 5];
star_t A[MaxM + 5];
int Par[MaxV + 5], Fa[MaxLog + 1][MaxV + 5], Dep[MaxV + 5], Weight[MaxV + 5], Lson[MaxV + 5], Rson[MaxV + 5];
int Siz[MaxV + 5], Wson[MaxV + 5], Top[MaxV + 5];
std::vector<int> Vec[MaxV + 5];
long long F[MaxV + 5], Suf[MaxV + 5];
void init() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i) scanf("%d", &Height[i]);
N++; Height[N] = N;
scanf("%d", &M);
for (int i = 1; i <= M; ++i) {
scanf("%d %d %d", &A[i].x, &A[i].y, &A[i].c);
Sum += A[i].c;
}
}
int find(int x) { return x == Par[x] ? x : Par[x] = find(Par[x]); }
void dfs(int u) {
Siz[u] = 1;
if (u <= N) return;
Dep[Lson[u]] = Dep[u] + 1;
Fa[0][Lson[u]] = u;
for (int i = 1; (1 << i) <= Dep[Lson[u]]; ++i)
Fa[i][Lson[u]] = Fa[i - 1][Fa[i - 1][Lson[u]]];
Dep[Rson[u]] = Dep[u] + 1;
Fa[0][Rson[u]] = u;
for (int i = 1; (1 << i) <= Dep[Rson[u]]; ++i)
Fa[i][Rson[u]] = Fa[i - 1][Fa[i - 1][Rson[u]]];
dfs(Lson[u]), dfs(Rson[u]);
Siz[u] += Siz[Lson[u]] + Siz[Rson[u]];
if (Siz[Lson[u]] > Siz[Rson[u]]) Wson[u] = Lson[u];
else Wson[u] = Rson[u];
}
void dfs(int u, int chain) {
Top[u] = chain;
if (u <= N) return;
dfs(Wson[u], chain);
dfs(Wson[u] == Lson[u] ? Rson[u] : Lson[u], Wson[u] == Lson[u] ? Rson[u] : Lson[u]);
}
inline int binSearch(int u, int val) {
for (int i = MaxLog; i >= 0; --i) {
if (Fa[i][u] == 0) continue;
if (Weight[Fa[i][u]] < val) u = Fa[i][u];
}
return u;
}
inline long long queryPath(int u, int f) {
long long res = 0;
while (Top[u] != Top[f]) {
res += Suf[Top[u]] - Suf[u];
u = Fa[0][Top[u]];
res += F[Wson[u]];
}
res += Suf[f] - Suf[u];
return res;
}
void bfs() {
static int que[MaxV + 5], ind[MaxV + 5];
int head = 1, tail = 0;
for (int i = N + 1; i <= V; ++i) ind[i] = 2;
for (int i = 1; i <= N; ++i) que[++tail] = i;
while (head <= tail) {
int u = que[head++];
if (u > N) {
F[u] = F[Lson[u]] + F[Rson[u]];
Suf[u] += Suf[Wson[u]];
}
for (int id : Vec[u]) {
int x = A[id].x, c = A[id].c;
F[u] = std::max(F[u], queryPath(x, u) + c);
}
if (u != V && Wson[Fa[0][u]] != u) Suf[Fa[0][u]] += F[u];
ind[Fa[0][u]]--;
if (ind[Fa[0][u]] == 0) que[++tail] = Fa[0][u];
}
}
void solve() {
static edge_t e[MaxN + 5];
for (int i = 1; i < N; ++i) e[i] = edge_t(i, i + 1, std::max(Height[i], Height[i + 1]));
std::sort(e + 1, e + N);
for (int i = 1; i <= N; ++i) Par[i] = i;
V = N;
for (int i = 1; i < N; ++i) {
int u = e[i].u, v = e[i].v, w = e[i].w;
int p = find(u), q = find(v);
V++;
Par[p] = Par[q] = Par[V] = V;
Weight[V] = w;
Lson[V] = p, Rson[V] = q;
}
dfs(V), dfs(V, V);
for (int i = 1; i <= M; ++i) {
int f = binSearch(A[i].x, A[i].y);
Vec[f].push_back(i);
}
bfs();
printf("%lld\n", Sum - F[V]);
}
int main() {
init();
solve();
return 0;
}
Day3 T2 Harvest
题意
有一个长度为 \(L\) 的环,有 \(N\) 名员工,\(M\) 棵果树,第 \(i\) 名员工位于 \(A_i\),第 \(i\) 棵果树位于 \(B_i\)。\(A_i,B_i\) 这 \(N+M\) 个数两两不同。
每秒,每名员工会向 \(+1\) 移动一格。一个员工如果到达了一棵苹果树,并且树上有苹果,他就会采下一个苹果。采苹果时间忽略不计。
每棵苹果树上最多只会有一个苹果。如果一个苹果被采下,苹果树要花费 \(C\) 的时间才会长出一个新苹果。
有 \(Q\) 次询问,每次询问给出两个数 \(V_i, T_i\),询问对于第 \(V_i\) 号员工,进行 \(T_i\) 时间后,采了多少苹果。
\(L, C \le 10 ^ 9\),\(N, M \le 2 \times 10 ^ 5\),\(T_i \le 10 ^ {18}\)。
题解
不会,先咕了。
Day3 T3 Stray Cat
题意
这是一道通信题。有两个人 Anthony 和 Catherine,需要分别实现他们的程序。
给出一张 \(n\) 个点,\(m\) 条边的图。Anthony 可以在边上放置标记,标记一共有 \(a\) 种。Catherine 一开始在某个节点,她需要通过 Anthony 提前放置的标记获取信息,在多余步数不超过 \(b\) 步内到达 \(0\) 号节点。
Catherine 获取的信息是,对于当前的点,除了走来的那条边以外,每种标记对于的其它边的数量。她可以选择一个颜色走,也可以沿着刚来的那条边往回走。
请设计 Anthony 和 Catherine 的策略。
\(n, m \le 20\ 000\)。
有两类数据:
- \(a = 3, b = 0\);
- \(a = 2, m = n - 1, b = 6\)。
题解
分两类解决。
第一类 \(a = 3, b = 0\)
注意到 \(\binom{3}{2} = 3\),我们可以根据相邻的颜色种类数来确定哪一条是应当走的边。我们不妨令 Catherine 的策略为,若相邻的颜色只有一种,就走那一种;否则相邻的颜色为两种,若为 \(0, 1\),选择颜色 \(0\),若为 \(1, 2\),选择颜色 \(1\),若为 \(2, 0\),选择颜色 \(0\)。接下来构造 Anthony 的方案使他能够正确引导凯瑟琳。
从 \(0\) 号点开始跑原图的最短路,建出最短路树。根据三角形不等式,对于原来的每条边,要么连接相邻的两层,要么连接同一层内的两点。我们令连接相邻两层的边颜色按照深度依次为 \(0, 1, 2, 0, 1, 2, \ldots\),连接同一层内的边颜色为该层连向下一层的边的颜色以保证 Catherine 永远不会走。
这样就可以正确引导了,得分 \(15\) 分。
第二类 \(a = 2, m = n - 1, b = 6\)
以 \(0\) 号点为根建出树。
如果起点不是度数为 \(2\) 的点的话,处理起来是很容易的。把每条边按照深度交替着用 \(0, 1, 0, 1, \ldots\) 染色,然后可以轻松地判断哪一条是往父亲的边,然后不断走交替的颜色就可以走到根。
难点在于起点在一条链上的情况,这时候我们需要做三步定向,确定出哪个方向是向根的。一旦确定,就可以按照上述做法直接走到根,不用再走多余道路了。
考虑如何三步定向。
我们唯一能获得的信息就是这个链上每条边依次的颜色。向上走和向下走的唯一区别就是是把字符串翻转了一下。
也就是说,我们可以找到一个无限长的 \(01\) 串 \(s\),用它给链染色,使得存在一个长度 \(l\),使得 \(s\) 中任意一个长度为 \(l\) 的子串,都不是 \(s\) 的翻转串 \(\operatorname{rev}(s)\) 的子串。
通过一些神奇的摸索,可以找到 \(s = \underbrace{011001}_{\text{循环节}}011001011001\cdots\),\(l = 5\)。这是满足条件的最小 \(l\)。
这个 \(l = 5\) 也正是我们走三步所能获得的最多信息。因为我们不仅知道三步各自的信息,也知道起点另一条边的颜色和终点另一条边的颜色,就总共知道了相邻 \(5\) 条边的信息。于是我们就能确定方向了。这样就做完了。
实现时需要注意各种特判,得分 \(85\) 分。
代码
Anthony.cpp
#include "Anthony.h"
#include <cstring>
namespace {
const int MaxN = 20000, MaxM = 20000;
const int s[6] = {0, 1, 1, 0, 0, 1};
int N, M, A, B;
bool OnChain[MaxN + 5];
int Dep[MaxN + 5];
std::vector<int> U, V;
std::vector<int> Ans;
struct Graph {
int cnte;
int Head[MaxN + 5], To[MaxM * 2 + 5], Next[MaxM * 2 + 5];
Graph() { cnte = 1; }
inline void addEdge(int from, int to) {
cnte++; To[cnte] = to;
Next[cnte] = Head[from]; Head[from] = cnte;
}
};
Graph Gr;
void bfs() {
static int que[MaxN + 5];
int head = 1, tail = 0;
memset(Dep, 0x7F, sizeof Dep);
Dep[0] = 0;
que[++tail] = 0;
while (head <= tail) {
int u = que[head++];
for (int i = Gr.Head[u]; i; i = Gr.Next[i]) {
int v = Gr.To[i];
if (Dep[u] + 1 < Dep[v]) {
Dep[v] = Dep[u] + 1;
que[++tail] = v;
}
}
}
}
void dfs(int u, int f, int c, int chaincount) {
int sons = 0;
for (int i = Gr.Head[u]; i; i = Gr.Next[i]) {
int v = Gr.To[i];
if (v != f) sons++;
}
if (u != 0 && sons == 1) {
OnChain[u] = true;
if (chaincount == -1 && c == 0) chaincount = 0;
chaincount = (chaincount + 1) % 6;
c = s[chaincount];
for (int i = Gr.Head[u]; i; i = Gr.Next[i]) {
int v = Gr.To[i];
if (v == f) continue;
Ans[(i >> 1) - 1] = c;
dfs(v, u, c, chaincount);
}
} else {
chaincount = -1;
c ^= 1;
for (int i = Gr.Head[u]; i; i = Gr.Next[i]) {
int v = Gr.To[i];
if (v == f) continue;
Ans[(i >> 1) - 1] = c;
dfs(v, u, c, chaincount);
}
}
}
std::vector<int> solve() {
for (int i = 0; i < M; ++i)
Gr.addEdge(U[i], V[i]), Gr.addEdge(V[i], U[i]);
Ans.resize(M);
if (A == 2) dfs(0, 0, 0, -1);
else {
bfs();
for (int i = 0; i < M; ++i)
Ans[i] = std::min(Dep[U[i]], Dep[V[i]]) % 3;
}
return Ans;
}
}
std::vector<int> Mark(int N, int M, int A, int B, std::vector<int> U, std::vector<int> V) {
::N = N;
::M = M;
::A = A;
::B = B;
::U = U;
::V = V;
return ::solve();
}
Catherine.cpp
#include "Catherine.h"
#include <cstring>
namespace {
const int MaxN = 20000;
int A, B, precol;
int cnt = 0, st = 0;
bool got = false;
int arr[32];
std::vector<int> y;
int straight() {
if (y[0] == 0) return precol = 1;
if (y[1] == 0) return precol = 0;
precol ^= 1;
return precol;
}
int solve() {
if (A > 2) {
if (y[0] == 0 && y[1] == 0) return 2;
if (y[0] == 0 && y[2] == 0) return 1;
if (y[1] == 0 && y[2] == 0) return 0;
if (y[0] > 0 && y[1] > 0) return 0;
if (y[1] > 0 && y[2] > 0) return 1;
if (y[2] > 0 && y[0] > 0) return 2;
return -1;
} else {
if (got == true) return straight();
cnt++;
if (cnt == 1) {
if (y[0] + y[1] != 2) {
got = true;
if (y[0] == 1) return precol = 0;
else return precol = 1;
} else {
if (y[0] == 2) st = 0, precol = 0;
else if (y[1] == 2) st = 3, precol = 1;
else st = 2, precol = 0;
return precol;
}
} else {
if (y[0] + y[1] != 1) {
if (y[0] == 0 || y[1] == 0) {
got = true;
return -1;
} else {
got = true;
return straight();
}
}
int c = (y[0] == 0 ? 1 : 0);
st = (st << 1) + c;
if (cnt == 4) {
if (arr[st] == 2) {
got = true;
return precol = c;
} else {
got = true;
return -1;
}
} else return precol = c;
}
}
}
}
void Init(int A, int B) {
memset(::arr, 0, sizeof ::arr);
::arr[12] = 1;
::arr[25] = 1;
::arr[18] = 1;
::arr[5] = 1;
::arr[11] = 1;
::arr[22] = 1;
::arr[19] = 2;
::arr[6] = 2;
::arr[13] = 2;
::arr[26] = 2;
::arr[20] = 2;
::arr[9] = 2;
::A = A;
::B = B;
}
int Move(std::vector<int> y) {
::y = y;
return ::solve();
}
Day4 T1 Capital City
题意
有一棵 \(n\) 个点的树,\(k\) 种颜色。每个节点有一种颜色。
求最少的颜色数量,使得这些颜色的点在树上构成了一个连通块。
\(n \le 2 \times 10 ^ 5\)。
题解
考虑建图。若存在一条 \(i \to j\) 的边,就表示选取了颜色 \(i\) 就必须选取颜色 \(j\) (即颜色 \(i\) 的块内存在一个颜色为 \(j\) 的点)。则答案是缩完点后,所有没有出度的点的最小大小。
对每种颜色求 LCA,则会发现,存在 \(i \to j\) 的充要条件就是,存在两个点 \(u, v\),满足 \(u\) 的颜色为 \(i\),\(v\) 的颜色为 \(j\),且 \(v\) 在 \(u\) 到 \(i\) 颜色的 LCA 的路径上。这已经可以做到 \(\mathcal{O}(n ^ 2)\) 建图了。
一个点是向往祖先的一条链进行连边,显然可以倍增优化建图。
建完图后跑个 Tarjan 就好了。
时间复杂度 \(\mathcal{O(n \log n)}\),空间复杂度 \(\mathcal{O}(n \log n)\)。
这玩意儿常数有点大,可能需要稍微卡一卡。有同学写了树剖前缀和优化建图,最后一段用 zkw 线段树优化建图,然后在虚树上做 Tarjan,常数特小,只跑了 \(0.2 \text{ sec}\),非常神仙。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
const int MaxN = 200000, MaxLog = 18;
const int MaxV = MaxN * MaxLog + MaxN;
struct Graph {
int cnte;
int Head[MaxN + 5], Next[MaxN * 2 + 5], To[MaxN * 2 + 5];
inline void addEdge(int from, int to) {
cnte++; To[cnte] = to;
Next[cnte] = Head[from]; Head[from] = cnte;
}
};
int N, K, V, S;
int Col[MaxN + 5], Lca[MaxN + 5];
int Fa[MaxLog + 1][MaxN + 5], Dep[MaxN + 5], Id[MaxLog + 1][MaxN + 5];
int Bel[MaxV + 5], Low[MaxV + 5], Dfn[MaxV + 5], Sum[MaxV + 5], Oud[MaxV + 5], Dfc;
int Stk[MaxV + 5], Tp; bool InStack[MaxV + 5];
char Fl[1 << 26], *Lk = Fl;
Graph Gr;
std::vector<int> G[MaxV + 5];
inline int read() {
int x = 0; char c;
do c = *Lk++; while (c < '0' || c > '9');
do x = (x << 1) + (x << 3) + c - '0', c = *Lk++; while (c >= '0' && c <= '9');
return x;
}
void init() {
fread(Fl, 1, 1 << 26, stdin);
N = read(), K = read();
for (int i = 1; i < N; ++i) {
int u, v;
u = read(), v = read();
Gr.addEdge(u, v);
Gr.addEdge(v, u);
}
for (int i = 1; i <= N; ++i) Col[i] = read();
V = K;
}
void dfs(int u) {
for (int i = Gr.Head[u]; i; i = Gr.Next[i]) {
int v = Gr.To[i];
if (v == Fa[0][u]) continue;
Fa[0][v] = u;
Id[0][v] = ++V;
G[Id[0][v]].push_back(Col[u]);
Dep[v] = Dep[u] + 1;
for (int j = 1; (1 << j) <= Dep[v]; ++j) {
Id[j][v] = ++V;
G[Id[j][v]].push_back(Id[j - 1][v]);
G[Id[j][v]].push_back(Id[j - 1][Fa[j - 1][v]]);
Fa[j][v] = Fa[j - 1][Fa[j - 1][v]];
}
dfs(v);
}
}
inline int getLca(int u, int v) {
if (Dep[u] < Dep[v]) std::swap(u, v);
int d = Dep[u] - Dep[v];
for (int i = MaxLog; i >= 0; --i)
if (d & (1 << i)) u = Fa[i][u];
if (u == v) return u;
for (int i = MaxLog; i >= 0; --i)
if (Fa[i][u] != Fa[i][v]) {
u = Fa[i][u];
v = Fa[i][v];
}
return Fa[0][u];
}
void tarjan(int u) {
Low[u] = Dfn[u] = ++Dfc;
Stk[++Tp] = u;
InStack[u] = true;
for (int v : G[u]) {
if (Dfn[v] == 0) {
tarjan(v);
Low[u] = std::min(Low[u], Low[v]);
} else if (InStack[v] == true) Low[u] = std::min(Low[u], Dfn[v]);
}
if (Low[u] == Dfn[u]) {
S++;
do {
int x = Stk[Tp--];
InStack[x] = false;
Bel[x] = S;
if (x <= K) Sum[S]++;
} while (Stk[Tp + 1] != u);
}
}
void solve() {
dfs(1);
for (int i = 1; i <= N; ++i) {
if (Lca[Col[i]] == 0) Lca[Col[i]] = i;
else Lca[Col[i]] = getLca(Lca[Col[i]], i);
}
for (int u = 1; u <= N; ++u) {
int x = u, d = Dep[u] - Dep[Lca[Col[u]]];
for (int i = MaxLog; i >= 0; --i) {
if (d & (1 << i)) {
G[Col[u]].push_back(Id[i][x]);
x = Fa[i][x];
}
}
}
for (int i = 1; i <= V; ++i)
if (Dfn[i] == 0) tarjan(i);
for (int u = 1; u <= V; ++u)
for (int v : G[u])
if (Bel[u] != Bel[v]) Oud[Bel[u]]++;
int ans = K;
for (int i = 1; i <= S; ++i)
if (Oud[i] == 0 && Sum[i] > 0) ans = std::min(ans, Sum[i]);
printf("%d\n", ans - 1);
}
int main() {
init();
solve();
return 0;
}
Day4 T2 Legendary Dango Maker
题意
这是一道提交答案题。
有一个 \(r \times c\) 字符的数组,仅包含字符 P
, W
, G
。需要分出最多的三元组 P
, W
, G
,满足在横向、纵向、斜向依次为 P
, W
, G
或 G
, W
, P
,且分出的三元组没有相交。
\(r \le 500, c \le 500\)。
题解
显然是一个 NPC 的独立集问题。不会,先咕了。
吐槽一下这个评分方式,四舍五入真的好骚,我还没见过不是最优解还能光明正大拿满分的提答。
有同学直接退火过了,还踩了标程好几个点。过几天我去试试。
Day4 T3 Treatment Project
题意
有一个长度为 \(n\) 的数轴,一开始每个点都为黑色。
有 \(m\) 种操作 \((t_i, l_i, r_i, c_i)\),表示在第 \(t_i\) 秒末,将 \([l_i, r_i]\) 内所有点都变成白色,需要花费 \(c_i\) 的代价。
每秒初,每个与黑色点相邻的白色点都会变成黑色。
求使得所有点变成白色的最小花费。
\(n, t_i, c_i \le 10 ^ 9\),\(m \le 10 ^ 5\)。
题解
将所有 \(r_i\) 加一,让区间变成左闭右开,便于处理。
对于一个可行方案,一定存在一个顺序,使得排序后,都有该不等式成立:\(r_{i - 1} - l_i \ge \lvert t_i - t_{i - 1} \rvert\)。
理解方法就是,在两次操作的间歇期中,不会出现前面操作的位置且不在后面操作的区间内的点变成黑色。
满足了这样的条件,两个方案就能连起来,变成一个大的方案。我们需要从时间 \(1\) 开始连方案,一直连到时间 \(n\)。
这与最短路类似。从 \(s\) 向所有 \(l_i = 1\) 的 \(i\) 连边,从所有 \(r_i \gt n\) 的 \(i\) 向 \(t\) 连边,所有满足 \(r_i - l_j \ge \vert t_i - t_j \rvert\) 的 \(i\) 向 \(j\) 连边。每条边的长度为它起始点的代价。然后跑 \(s\) 到 \(t\) 的最短路,就是答案。
这与就有了一个 \(\mathcal{O}(m ^ 2 \log m)\) 的暴力,疯狂地卡常数可以卡到 \(39\) 分。
接下来需要优化建边,把绝对值拆掉并移项后,发现 \(i \to j\) 连边的充要条件是,\(r_i - t_i \ge l_j - t_j\) 且 \(r_i + t_i \ge l_j + t_j\)。
当然可以把每个点看做平面上的一个点 \((l_j - t_j, l_j + t_j)\),然后类似 NOI 2019 弹跳的做法,KD 树矩形连边优化最短路。复杂度是 \(\mathcal{O}(m \sqrt m)\)。特别注意常数优化,我是没卡过去。
另一种优化方式是,按照 \(l_j - t_j\) 排序,接着双指针加可持久化线段树来优化建边,最后直接跑 Dijkstra 求最短路。这种做法的常数小很多。可以轻松通过本题。
时间复杂度 \(\mathcal{O}(m \log m \log (n + \max t_i))\),空间复杂度 \(\mathcal{O}(m \log (n + \max t_i))\)。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
const int MaxN = 100000, MaxV = 3200000;
const long long INF = 0x7F7F7F7F7F7F7F7F;
struct project_t {
int t, l, r, v;
project_t(int _t = 0, int _l = 0, int _r = 0, int _v = 0) { t = _t, l = _l, r = _r, v = _v; }
inline friend bool operator<(const project_t &a, const project_t &b) { return a.l - a.t < b.l - b.t; }
};
int L, N, V;
project_t A[MaxN + 5];
int Rt[MaxN + 5], Lson[MaxV + 5], Rson[MaxV + 5];
long long Dis[MaxV + 5]; bool Vis[MaxV + 5];
std::vector< std::pair<int, int> > Gr[MaxV + 5];
std::priority_queue< std::pair<long long, int> > Pq;
char Fl[1 << 26], *Lk = Fl;
inline int read() {
int x = 0; char c;
do c = *Lk++; while (c < '0' || c > '9');
do x = (x << 1) + (x << 3) + c - '0', c = *Lk++; while (c >= '0' && c <= '9');
return x;
}
void init() {
fread(Fl, 1, 1 << 26, stdin);
L = read(), N = read();
for (int i = 1; i <= N; ++i) {
A[i].t = read(), A[i].l = read(), A[i].r = read(), A[i].v = read();
A[i].r++;
}
}
inline bool cmp(int x, int y) { return A[x].r - A[x].t < A[y].r - A[y].t; }
int insert(int pos, int id, int y, int l, int r) {
if (l == r) {
if (y != 0) Gr[id].push_back(std::make_pair(y, 0));
return id;
}
int x = ++V;
int mid = (int) ((1LL * l + r) >> 1);
if (pos <= mid) {
Lson[x] = insert(pos, id, Lson[y], l, mid);
Rson[x] = Rson[y];
} else {
Lson[x] = Lson[y];
Rson[x] = insert(pos, id, Rson[y], mid + 1, r);
}
if (Lson[x] != 0) Gr[x].push_back(std::make_pair(Lson[x], 0));
if (Rson[x] != 0) Gr[x].push_back(std::make_pair(Rson[x], 0));
return x;
}
void addedge(int u, int w, int left, int right, int x, int l, int r) {
if (x == 0) return;
if (left == l && right == r) {
Gr[u].push_back(std::make_pair(x, w));
return;
}
int mid = (int) ((1LL * l + r) >> 1);
if (right <= mid) addedge(u, w, left, right, Lson[x], l, mid);
else if (left > mid) addedge(u, w, left, right, Rson[x], mid + 1, r);
else addedge(u, w, left, mid, Lson[x], l, mid), addedge(u, w, mid + 1, right, Rson[x], mid + 1, r);
}
void build() {
V = N;
static int lnk[MaxN + 5];
for (int i = 1; i <= N; ++i) lnk[i] = i;
std::sort(lnk + 1, lnk + 1 + N, cmp);
for (int I = 1, j = 0; I <= N; ++I) {
int i = lnk[I];
while (j < N && A[j + 1].l - A[j + 1].t <= A[i].r - A[i].t) {
j++;
Rt[j] = insert(A[j].l + A[j].t, j, Rt[j - 1], 1, 2000000001);
}
addedge(i, A[i].v, 1, A[i].r + A[i].t, Rt[j], 1, 2000000001);
}
}
void dijkstra() {
memset(Dis, 0x7F, sizeof Dis);
for (int i = 1; i <= N; ++i)
if (A[i].l == 1) {
Dis[i] = 0;
Pq.push(std::make_pair(-Dis[i], i));
}
while (!Pq.empty()) {
int u = Pq.top().second; Pq.pop();
if (Vis[u] == true) continue;
Vis[u] = true;
for (auto p : Gr[u]) {
int v = p.first, w = p.second;
if (Dis[u] + w < Dis[v]) {
Dis[v] = Dis[u] + w;
Pq.push(std::make_pair(-Dis[v], v));
}
}
}
}
void solve() {
std::sort(A + 1, A + 1 + N);
build();
dijkstra();
long long ans = INF;
for (int i = 1; i <= N; ++i)
if (A[i].r > L) ans = std::min(ans, Dis[i] + A[i].v);
if (ans == INF) puts("-1");
else printf("%lld\n", ans);
}
int main() {
init();
solve();
return 0;
}