JOISC2020 自闭记

以下是我考场上的思路,很多题都不是正解。对于某些题目,我们使用《代码部落》中的题解,希望大家能够看懂

JOISC2020 Round1 自闭记

T1

11 pts 算法:考虑\(DP\)

\(f_{i, j, k}\)表示前\(i\)个数,选了\(j\)\(B\)数组的数,且第\(i\)个数选的是\(A/B\)时,是否存在所要求的单调不降的数列。

直接\(DP\),可以获得一个\(O(n^2)\)的做法。

100 pts 算法:

我们通过猜结论/打表发现一个规律。如果固定了\(i, k\)的话,那么使得\(f_{i, j, k} = 1\)\(j\)的集合是一段连续的区间。

因此,我们可以缩减状态。

记录\(i, k\)固定的时候使得\(f_{i, j, k} = 1\)\(j\)的最小和最大值。

这样转移变成取\(max/min\)

考虑如何还原方案。

倒着填,通过\(DP\)结果判断是否能填A或者B。能填\(A\)则填\(A\),否则填\(B\)

时间复杂度\(O(n)\),可以获得\(100\)分。

大概证明:对\(i\)归纳证明固定\(i, k \in \{0, 1 \}\)时,\(j\)的取值是一段区间,且固定\(i\),\(k\)\(0,1\)之间的任意值时\(j\)的取值是一段区间。

T2

本人考场上只拿了\(3\)分。

\(2\)分做法:

\(k = 1\)时,这个点在\(n\)个长方形的交集内。

每个长方形的横坐标取值为\([L_i, R_i]\),纵坐标取值为\([D_i, U_i]\)

我们只需要求\(L\)的最大值,\(R\)的最小值,\(D\)的最大值,\(U\)的最小值。

然后在这上面任意放一个点即可。

\(3\)分做法:

\(k = 2, n \leq 2000\)时。

我们考虑求出\(R\)的最小值\(r\)。我们可以发现可以通过调整使得存在一个点的横坐标为\(r\)。枚举它的纵坐标(在这\(2n\)个D数组和U数组的值中枚举),然后把它能覆盖的长方形删掉。再对剩下的长方形套用\(k = 1\)的算法。

时间复杂度\(O(n^2)\),可以通过\(3\)分。

\(6\)分做法:

\(k = 2, n \leq 200000\)时,我们考虑从小到大对\(r\)做扫描线,在扫描线的过程中动态维护剩下的长方形中\(L, D\)的最大值和\(R, U\)的最小值。

实现时可以用\(4\)个堆来维护。

时间复杂度\(O(n \log n)\),可以通过\(6\)分。

\(k = 3\)的做法:

实际上\(k \leq 3\)的时候可以\(O(n)\)

我们求出\(L\)的最大值\(Lmax\)\(R\)的最小值\(Rmin\),\(D\)的最大值\(Dmax\)\(U\)的最小值\(Umin\)

如果\(Lmax \leq Rmin\),转换为\(1\)维问题,用区间覆盖算法。

否则形成的矩形的\(4\)条顶点中总有一个点要被选。

枚举这个点被不被选,递归下去求解即可。

时间复杂度\(O(n)\)

\(k = 4, n \leq 2000\)的做法:

这时候我们需要考虑\(4\)条边中每条边恰好选\(1\)个点的情况。

枚举一条边上的一个点(通过离散化,它可以只有\(O(n)\)种取值),再套用\(k \leq 3\)的算法,可以获得一个\(O(n^2)\)的算法。

通过这些分析,我们可以获得\(21\)分。

正确性没有保证的做法:

随机\(k\)个矩形,将剩余的矩形随机排列。我们按照这个排列的顺序把每个矩形分为\(k\)类中的一类。假设排在它前面的矩形已经分划完毕,我们算出如果把这个矩形加入第\(i\)个集合,那么计算出这个集合的新的矩形交的面积大小/原来的面积大小。我们找其中最大者,加入那个集合。

我们多随机几次,直到划分出来的\(k\)组矩形都交集非空为止。

正确性有保证的做法:

考虑我们之前还剩下一个矩形且这个矩形的\(4\)条边上各有一个点的情况。使用\(2-SAT\)解决这个问题。

设这个矩形的横坐标范围为\([L,R]\),纵坐标范围为\([D,U]\)

比如说一个点的横坐标为\(L\),且纵坐标为\([D,U]\)中的某个数\(y\)。容易发现可以离散化使得纵坐标只有\(O(n)\)种取值。

我们把矩形纵坐标边界的值拿来离散化,作为关键点\(y_1 \leq ... \leq y_k\)。对这条边上的一个点,设立\(k\)\(01\)变量,分别表示\(y \leq y_i\)

然后我们发现\(y \leq y_i \rightarrow y \leq y_{i + 1}\)。于是我们可以用\(2-SAT\)刻画这条边上恰好选一个点的条件。

接下来我们要刻画每个矩形中至少有一个点的条件。

如果是两条边的至少一个区间上有一个点,相当于要求\(y_1 \in [l_1, r_1] \or y_2 \in [l_2, r_2]\)

把这个条件转换成如下\(4\)组推导关系。

\(y_1 < l_1 \rightarrow y_2 \ge l_2, y_1 < r_1 \rightarrow y_2 \leq r_2\)

\(y_1 > r_1 \rightarrow y_2 \ge l_2\)

\(y_1 > r_1 \rightarrow y_2 \leq r_2\)

发现如果矩形不包含一个整的边的话,每个矩形中至少有一个点可以用\(2-SAT\)来表示。

可以用\(2-SAT\)解决这一种情况了。

T3

简要题意:一开始你有\(m\)个二维平面上的点\((x_i, y_i)\)\(x_i + y_i \leq n\)

然后有\(4\)种操作,

1.询问标号为\(P\)的点的横纵坐标

2.对于所有\(y_i \leq L\)的点,\(x_i \leftarrow \max (x_i, n - L)\)

3.对于所有\(x_i \leq L\)的点,\(y_i \leftarrow max(y_i, n - L)\)

4.增加一个点\((X, Y)\),它的标号是之前所有出现的点的个数 \(+ 1\)

时限:\(11 s\)

数据范围:\(m \leq 500000, q \leq 10^6\)

考场上这题写了\(22\)分。

\(1\)分算法:

对于\(m \leq 2000, q \leq 5000\)的测试点,我们直接暴力\(O(mq + q^2)\)的求解。

只有\(1,2,4\)操作时:

考虑把问题转换为求一段时间区间里面的\(l \ge x\)的所有\(l\)的最小值。我们可以用可持久化线段树来实现。

只有\(1,2, 3\)\(x\)非严格递增,\(y\)非严格递减时:

考虑操作过程中永远满足\(x\)非严格递增,\(y\)非严格递减。

所以每次操作影响的标号是一段区间,且影响为\(y\)坐标赋为某个值,或者\(x\)坐标赋为某个值。可以在线段树上维护

\(x\)的最小值,\(x\)的最大值,\(y\)的最小值,\(y\)的最大值,\(x\)的赋值标记,\(y\)的赋值标记这六个变量来实现线段树上二分找区间以及线段树上修改的操作。

我们终于获得了\(22\)分。

满分做法

首先考虑\(65\)分做法,即没有插入操作。注意到所有被至少修改一次的节点中,如果\(x_i < x_j\),那么\(y_i \ge y_j\),证明很显然。

上述结论等价于将所有节点按\(x\)关键字排序,\(y\)为第二关键字排序时,所有节点\(y\)坐标从左往右非严格单调递增。

每次修改相当于修改\(x \leq a, y \leq b\)的点,根据以上结论,其实等价于按如下方式排序时,修改一段连续的区间。

我们对每个节点使用动态开点线段树 + 二分计算出每个节点最先修改的时间。然后我们在平衡树在对应的时间中支持加入节点和区间修改。

上述结论对新加入的节点不成立,于是我们可以利用二进制分组做进行定期的重构,这样每个节点至多被重构\(log\)次,每次修改只要对\(log\)棵平衡树进行修改,复杂度为\(O(n \log^2 n)\)

JOISC 2020 Round2 自闭记

T1

\(O(n^2)\)算法:

考虑询问任意一对点\((u, v)\),如果答案为\(1\)的话,得到\(u\)颜色与\(v\)相同或$u \space loves \space v \(或\)v \space loves \space u$。

接下来我们考虑如何区分这三类边。

对一个点\(u\),如果\((u, v_1), (u, v_2), (u, v_3)\)是我们得到的点,那么我们询问\((u, v_1, v_2), (u, v_1, v_3), (u, v_2, v_3)\),会得到其中一组的答案是\(1\)。设为\((u, v_1, v_2)\)。我们可以得到\(u \space loves \space v_3\)。如果\(u\)度数为\(1\),那么和它相邻的点与它颜色相同。

这样我们可以得到所有的有向边,于是剩下的边就是满足端点颜色相同的边。

可以获得\(40\)分。

4分算法

问题变成询问一个点集里面出现不同颜色个数。

我们可以考虑二分,对每个点\(u\),设剩下的点是\(v_1, ...., v_{2n - 1}\)

询问\((u, v_1, ..., v_i)\),再询问\((v_1, ..., v_i)\)

如果第一个询问的答案比第二个多,说明\(u\)的颜色和\((v_1, .., v_i)\)是不同的。

这样我们可以二分出来和\(u\)的颜色相同的点。

Subtask 4 算法

如果我们知道每只变色龙的性别?

我们考虑对一个雄性的点\(u\),设雌性的点为\(v_1, ..., v_n\),那么我们询问:

\((u, v_1, ..., v_i)\),如果答案是\(i + 1\),那么就知道\(u\)\(v_1, ..., v_i\)没有边。

我们可以通过这个事实依次二分出\(3\)\(O(n^2)\)算法所要知道的边。

再使用\(O(n^2)\)算法的后半部分即可。

通过长途跋涉我们获得了\(64\)分。

某个\(O(n \log n)\)算法:

目前此算法的常数较大且不太容易优化,我们先咕着不讲吧。

By zbw

另外一个:

By wyp

我们考虑将所有有颜色相等和love关系的点连边构成一个图\(G\)

首先我们发现如果\(S\)是独立集,且\(u\)不在\(S\)中。

当且仅当Query \(S \cup \{ u \}\) 的,答案为\(\lvert S \rvert + 1\)\(S \cup \{ u \}\)的并集也是独立集。

我们考虑在所有的点中,随机一个顺序,然后从左往右如果能加进独立集就加,会得到一个比较大的独立集。

(\(\ge 点数/4\),可以算两次,发现每个不在独立集的点都与独立集连了至少一条边,且每个点的度数\(\leq 3\))。

把所有不在独立集的点\(u\)去向独立集这个序列二分,会得到所有不在独立集的点连向在独立集的点的边。

然后把这个较大的独立集删掉。不断这样子做,直到最后所有点都删除过。

下同\(O(n^2)\)的后半部分。

需要注意常数。

T2

维护所有双向边构成的连通块。

我们发现要维护的东西有:

1.对每个连通块\(i\),维护一个\(set\)\(from_i\),所有连向它的点。

2.对每个点\(i\),维护一个\(set\)\(to1_i\),表示它连向的连通块的集合。

3.对每个连通块\(i\),维护一个\(set\)\(to2_i\),表示它连向的连通块。

4.对每个连通块\(i\),维护一个\(set\)\(comp_i\),表示它内部的点数。

维护一个队列,表示可能需要缩的两个连通块。

每次在成功缩连通块后,

1.更新答案。

2.把增加的可能要缩的连通块\(push\)进队列。

连通块合并的过程我们可以用启发式合并来获得更优的复杂度。

关键代码如下:

void merge (int u, int v) {
	int ru = findroot(u), rv = findroot(v);
	if (ru == rv) return ;
	if (!to1[u].count(rv)) {
		to1[u].insert(rv), to2[ru].insert(rv);
		from[rv].insert(u), ans += sz[rv];
	}
	if (!to2[rv].count(ru)) return ;

	ans -= 1ll * sz[ru] * from[ru].size();
	ans -= 1ll * sz[rv] * from[rv].size();
    ans -= 1ll * sz[ru] * (sz[ru] - 1) / 2;
    ans -= 1ll * sz[rv] * (sz[rv] - 1) / 2;
	if (sz[ru] > sz[rv]) swap(ru, rv);

	for (set<int> :: iterator it = comp[ru].begin(); it != comp[ru].end(); it++) {
		int x = *it;
		comp[rv].insert(x), from[rv].erase(x);
		for (set<int> :: iterator i = to1[x].begin(); i != to1[x].end(); i++) q.push(make_pair(x, *i));
	}
	comp[ru].clear();
	
	for (set<int> :: iterator it = from[ru].begin(); it != from[ru].end(); it++) {
		int x = *it, rx = findroot(x);
		to1[x].erase(ru), to2[rv].erase(ru);
		if (rx != rv) {
			to1[x].insert(rv), to2[rx].insert(rv);
			from[rv].insert(x);
			q.push(make_pair(x, rv));
		}
	}
	from[ru].clear();

	for (set<int> :: iterator it = to2[ru].begin(); it != to2[ru].end(); it++) {
		int x = *it;
		if (x != rv) to2[rv].insert(*it);
	}
	to2[ru].clear();

	ans += 1ll * (sz[ru] + sz[rv]) * from[rv].size();
	ans += 1ll * (sz[ru] + sz[rv]) * (sz[ru] + sz[rv] - 1) / 2;
	fa[ru] = rv, sz[rv] += sz[ru];
}
	for (int i = 1; i <= m; i++) {
		int u, v;
		read(u), read(v);
		q.push(make_pair(u, v));
		while (!q.empty()) {
			pair<int, int> pi = q.front();
			q.pop(), merge(pi.first, pi.second);
		}
		write(ans), putchar('\n');
	}

若使用\(hash\)表,复杂度可以做到\(O((n + m) \log n)\)。否则为\(O((n + m) \log^2 n)\)。可以获得\(100\)分。

T3

考虑给定\({a}\),原序列等价于每次将序列\({a}\)中最大的两个值\(u, v\),将它们加入集合\(S\),并pop出最大的那个值。考虑所有pop出的值即为最后剩余的石柱。

考虑换一种方式理解,其实等价于下标从大到小枚举所有石柱,找到最大的正整数\(x\)使得\(x \leq a_i\)\(x \ge 1\)\(x\)未被占据,那么将\(x\)占据。

根据以上方式进行dp,设\(f_{i, j}\)表示处理了后\(i\)个下标的石柱,前\(j\)个高度被占据的方案数。根据以上位置是否被占据进行转移。

若当前位置被删除,则可以直接计算出方案数。否则枚举以下新增的连续段长度进行转移。

JOISC2020 Round3 自闭记

T1

\(O(n^2)\)算法:

把问题转换为放代价和最大的黄点,使得每个没有白点的子矩形最多有一个黄点。

考虑先建出来笛卡尔树,然后设\(f_{u, i}\)表示在横坐标\(u\)的子树代表的区间,且在\(A_u\)上边的黄点的纵坐标为\(i\)时,所有黄点的最大收益。设\(g_u\)表示在\(A_u\)上面没有黄点时这一块黄点的最大收益。

转移时我们需要枚举最上面的黄点的横坐标在哪一块。(在左子树/\(u\)/右子树)。

可以获得\(35\)分。

\(O((n + m) \log n)\)算法:

考虑用线段树合并优化之前的算法。

DP优化 -> 把其中一维看成函数/序列,用数据结构维护这一维的变化。

我们把DP的第二维看成序列。

发现转移的变化可以用区间加/区间询问max/两个线段树代表的数组取max这三种操作来实现。

于是我们用一个标记永久化的线段树即可通过此题。

T2

考虑第\(i\)个人摘了当前苹果后,下一个摘当前苹果的人是谁,可以连出一个\(n\)条边\(n\)个点的有向图。显然是由一个基环内向树和一些链组成。只需要利用数据结构在图上进行模拟题的操作,即可计算出答案。难点在代码的实现。

T3

\(4\)个Subtask

我们考虑按深度\(\mod 3\)染色。

先对整张图做BFS,求出每个点的深度。

然后对于每条边\((u, v)\),这条边染\(\min(dep_u, dep_v) \mod 3\)

考虑第二个程序怎么写?

假设她当前所在的点深度为\(d\),那么和它相连的边中要么标号是\(d \bmod 3\),要么是\((d - 1) \mod 3\)

且至少有一条是\((d - 1) \mod 3\)

如果所有边标号是相同的,就返回那个标号。

否则标号是有\((d - 1) \mod 3\),也有\(d \mod 3\)。我们可以唯一确定哪一个是\((d - 1) \mod 3\)

可以通过\(15\)分。

链的特殊做法

对这条链循环染\(110010\)。我们第一步先随便找一个方向走。记录之前走过的颜色。如果我们走\(3\)步过后发现这三步走过的颜色只能是从上往下走走出来的颜色串,然后我们就再往回走\(3\)步即可。

树的特殊做法

Subtask 5

我们考虑按深度\(\bmod 2\)染色。对每条边\((u,v)\)\(\min(dep_u, dep_v) \bmod 2\)

所有数据

我们考虑先将树划分成若干条链,如果一个点有两个儿子,就称它为分叉点。我们希望染色里面这种条件能满足:

1.每个分叉点中到父亲的边和到儿子的边全部不相同

2.每一条链上都是\(110010110010....\)的循环移位。

第二个条件的目的是让它不同方向的长度为\(5\)的子串是不一样的。

第二个程序的人:

她第一步可以记录\(2\)条边的颜色,如果她一直往下走了\(3\)步并且即将走第\(4\)步的时候发现她看到的\(5\)条边只能是从上往下走形成的\(5\)条边,那么她就开始不断往回走。否则她继续往前走。

如果她走到分叉点,那么她可以判断出来父亲的边,朝着它往上走即可。

如果她走到非分叉点,我们需要判断她走下一条边后是否是走在错误的方向上。如果是,就往回走,否则继续往下走。

细节比较多。

本人考场上\(71\)分部分分的代码如下:

int Move (vector<int> y) {
	bool flag[2] = {false, false};
	if (first) {
		for (int i = 0; i < 2; i++) {
			if (y[i]) {
				first = false, last_go = i;
				info += (i == 0 ? "0" : "1");
				return i;
			}
		}
	}
	else {
		int edge_cnt = y[0] + y[1];
		if (edge_cnt == 0) {
			first_fail = true;
			return -1;
		}
		if (edge_cnt == 1) {
			if (y[0]) {
				if (first_fail) {
					last_go = 0;
					return 0;
				}
				else {
					info += "0";
					if (se.count(info)) {
						first_fail = true;
						return -1;
					}
					last_go = 0;
					return 0;
				}
			}
			else {
				if (first_fail) {
					last_go = 1;
					return 1;
				}
				else {
					info += "1";
					if (se.count(info)) {
						first_fail = true;
						return -1;
					}
					last_go = 1;
					return 1;
				}
			}
		}
		else {
			if (y[0] > 1 && !y[1]) {
				first_fail = true;
				return -1;
			}
			if (y[1] > 1 && !y[0]) {
				first_fail = true;
				return -1;
			}
			last_go ^= 1;
			return last_go;
		}
	}
}

JOISC Round 4自闭记

T1

有一棵树,每个点有\(k\)种颜色中的一种。现在你要把一种颜色\(y\)的点全部变成颜色\(x\)。称这种操作为合并操作。问你最少要多少次合并操作才能选出来一种颜色使得它们构成树上的一个连通的子图。

\(O(n \log n)\) 算法

把题意稍微转化为选出一个颜色的集合使得拥有这些颜色的点构成连通的子图。

考虑点分治,每次设分治重心为\(u\),那么我们求出来连通块包含\(u\)的答案。

考虑建一张图,以\(u\)为根,若一个点\(v\)的父亲是\(fa_v\)。则我们将\(v\)的颜色与\(fa_v\)连边。然后从\(u\)的颜色开始沿着这张图\(dfs\)一遍,即可判断出来最少要多少种颜色才能构成连通子图了。

注意我们每次分治完了过后要把跨过\(u\)的两个子树以及\(u\)所染的颜色标为不合法。

在之后\(dfs\)中如果发现必须要选这种颜色,就不更新答案了。

即可做到\(O(n \log n)\)

T2

本人在考场上拿了\(36\)分。以下是我\(36\)分的乱搞。

第一步:对所有白点,如果存在唯一的方向使得可以以白点为中心,选出一个PWG/GWP的一串的话,我们则将P和G连边。跑二分图最大匹配。

第二步:我们在这个解的基础上做爬山。如果我们再发现有白点可以以它为中心选一串的话,就继续选。

第三步:我们再枚举一个白点,然后改变它的方向,然后再看它附近的白点有没有能够再选一串的。

我们可以通过这个乱搞获得\(36\)分。

T3

本人考场上只拿了\(9\)分。

4分算法

对于\(T_i = 0\)的点,转换为选一些区间覆盖\([1,n]\)的所有点。离散化后优化\(dp\)即可。

5分算法

枚举每个治疗方案是否被选择,然后维护被感染的人组成的段即可模拟出在执行这些治疗方案后是否所有人都被治愈。

35/100算法

令对于第\(i\)个方案,\(f_i\)表示在\(t_i\)天,\([1, r_i]\)的人都康复的最小代价。那么考虑转移,\(i\)能转移到\(j\)当且仅当\(l_j \leq r_i - \lvert t_i - t_j \rvert + 1\)。可以转换成图的模型,跑最短路。利用数据结构优化连边,复杂度为\(O(m \log^2 m)\)

posted @ 2020-03-22 08:44  unzcjouhi  阅读(1446)  评论(0编辑  收藏  举报