「考试总结」ZROI-21-CSP7连-DAY2 总结
#T1 IP 地址
#题意简述
一个合法的 IP 地址每位数字最小为 \(0\),最大为 \(255\),且不含前导零。每位数字中间有一个 "." 隔开。
给定一个文本串,其中最多包含 \(4\) 个整数(可能极大),问是否是合法的 IP 地址,如果不合法请更正为合法 IP,数字大于 \(255\) 则修改为 \(255\).
#大体思路
把快读魔改一下就好了,但是一定要注意细节,比如快读实际会向后多读一个字符,可以借此判断最后是否有多余字符。
#Code
1 | const int N = 100010; |
#T2 字符串
#题意简述
给定一个长度为 \(n\) 的字符串,其中一共包含两种字符:A
和 P
,其中,AP
和 PP
可以消去,消去后剩下两段字符串会拼在一起,问经过若干次消除后能得到的最小字符串长度为多少。
#大体思路
注意到 A
只能由 AP
消去,而 P
可以由 PP
消去,考虑贪心:当前的 A
要么在后面被消去,要么无法消去,而多出来的 P
对答案的贡献最多是 \(1\),所以优先消掉 A
一定是最优的选择。
#Code
1 | const int N = 10010; |
#T3 继承类
#题意简述
实在不好概述,这里直接粘来原题面。
现在发明了一种类似于 C++ 的面向对象编程语言中的类声明。
每个类声明的格式为 "\(K : P_1\ P_2\ \dots\ P_K;\)"
其中 \(K\) 是要声明的新类的名称,\(P_1\) 、\(P_2\) 、\(\dots\) 、\(P_k\) 类 \(K\) 继承的类的名称。
例如,"shape : ;" 是不继承任何其他类的类 “shape” 的声明,而 “square : shape rectangle;” 是继承类 “shape” 和 “rectangle” 的类 “square” 的声明。
如果类 \(K_1\) 继承类 \(K_2\) ,类 \(K_2\) 继承类 \(K_3\) ,依此类推,直到类 \(K_{m−1}\) 继承类 \(K_m\) ,那么我们说所有类 \(K_1,K_2,\dots,K_{m−1}\) 派生自类 \(K_m\) 。
编程语言的规则禁止循环定义,因此不允许从自身派生一个类。换句话说,类层次结构形成了一个有向无环图。此外,不允许在类层次结构中出现所谓的菱形。一个菱形由四个不同的类 \(A\)、\(B\)、\(X\)、\(Y\) 组成,而且它满足(如下图所示):
- 类 \(X\) 和 \(Y\) 派生自 \(A\)。
- 类 \(B\) 派生自 \(X\) 和 \(Y\)。
- 类 \(X\) 不是从 \(Y\) 派生的,类 \(Y\) 也不是从 \(X\) 派生的。
现在你会获得 \(n(n\le 1000)\) 个要按顺序处理的类声明,并确定每个类声明是否是正确声明。
正确声明的类被添加到层次结构中,而错误的类被丢弃。声明 “\(K : P_1\ P_2\ \dots\ P_K;\)” 如果满足以下条件,则认为是正确声明:
- 类 \(K\) 尚未声明。
- 所有类别 \(P_1,P_2,\dots,P_k\) 之前已经声明过。请注意,此条件可确保类永远不会从其自身派生,或者类层次结构中不能存在循环。
- 通过添加继承了 \(P_1,P_2,\dots,P_k\) 的类 \(K\) 以后,类层次结构保持有序,即没有形成任何菱形。
现在需要你分别处理上述声明并确定每个声明的正确性。
#大体思路
直接考虑性质三:简单分析“菱形”的性质,不难发现不存在菱形的前提是所有的 \(P_i\) 要么是严格的祖孙关系,要么毫无关系,总之不能为旁系亲属,发现如果当前语句合法,那么所有的 \(P_i\) 的旁系亲属都是当前 \(K\) 的旁系亲属,很好维护,但是直接用 bool
数组维护关系的话时间复杂度为 \(O(n^3)\),不能接受,又发现维护的值要么是 \(0\) 要么是 \(1\),且支持位运算,直接上 bitset
,时间复杂度优化到 \(O(\dfrac{n^3}{w})\),可以艹过去了。
#Code
1 | const int N = 1010; |
#T4 子图
#题意简述
依旧是不好简述,上原题面。
Cuber QQ 的研究兴趣是在图 \(G=(V(G),E(G))\) 中找到最好的 \(k\)-degree 子图。
子图 \(S\) 是 \(G\) 的 \(k\)-degree 子图需要满足以下要求:
- 每个顶点 \(v(v\in S)\) 在 \(S\) 中至少有 \(k\) 度;
- \(S\) 是连通的 ;
- \(S\) 是极大的,即 \(S\) 的任何超图都不是 \(k\)-degree 子图,除了 \(S\) 本身。
然后 Cuber QQ 定义子图 \(S\) 的分数。在定义分数之前,他首先定义:
- \(n(S)\):子图 \(S\) 中的顶点数,即 \(n(S)=|V(S)|\);
- \(m(S)\):子图 \(S\) 的边数,即 \(m(S)=|E(S)|\);
- \(b(S)\):子图 \(S\) 中的边界边数,\(b(S)=|{(u,v)|(u,v)∈E(G),u∈V(S),v∉V(S),v∈V(G)}|\);
他定义一个子图的分数为 \(score(S)=M\cdot m(S)−N\cdot n(S)+B\cdot b(S)\),其中 \(M,N,B\) 是给定的常数。
子图的分数越高,Cuber QQ 认为它越好。你需要在图 \(G\) 中找到最好的 \(k\)-degree 子图。如果有许多 \(k\)-degree 子图的分数相同,则应最大化 \(k\)。输出最大的分数及对应的 \(k\).
#大体思路
首先,如果不考虑连通性问题,那么 \((k+1)\)-degree 一定是 \(k\)-degree 的子图,于是我们可以先求得一个标记,如果一个点属于 \(k\)-degree 而不属于 \(k+1\)-degree,那么该点标记为 \(k\),求得这个标记的过程可以通过像剥卷心菜一样将原图层层剥开,具体细节我们留到后面去说;
假设我们已经求得了每个点的标记,再来考虑通过将卷心菜原图一层层还原回去得到每个 \(k\)-degree 的 score;显然我们是通过加入标记为 \(k\) 的点得到 \(k\)-degree,考虑单个新加入的点,他们对 \(n,m,b\) 的贡献分别是什么。
先来引入一些新记号:
- \(E(u,>)\) 表示 \(u\) 的边中另一个点标记比 \(u\) 大的边的集合;
- \(E(u,<)\) 表示 \(u\) 的边中另一个点标记比 \(u\) 小的边的集合;
- \(E(u,=)\) 表示 \(u\) 的边中另一个点 \(v\) 标记与 \(u\) 相等但 \(v>u\) 的边的集合;
现在不难写出新的贡献:
- \(\Delta n=1\);
- \(\Delta m=|E(u,>)|+|E(u,=)|\);
- \(\Delta b=|E(u,<)|-|E(u,>)|\);
在合并的过程中,可能会导致几个连通块合成一个连通块,可以采用并查集提前维护出各个连通块的代表元素,代表元应当是整个连通块中标记最小的点,这样才能正确地完成合并。为下面合并答案,同时记录每个小连通块合并后的大连通块的编号,以及当前连通块的 \(k\).
并查集采用按秩合并+路径压缩,总体时间复杂度为 \(O(m+n)\),下面的代码仅采用了路径压缩,总体时间复杂度为 \(O(m\log n+n)\).
#一些细节实现
获取标记时,先用桶排将所有点按照度数从小到大排序,我们自小到大遍历这个序列,对于每个点,将与其相连的所有度数大于该点度数的点的度数减一,同时直接改变其在数列中的位置,将其放到原本度数的开头位置,对应度数的开头位置后移一位,这样维护可以保证在任意时刻队列中都是有序的。考虑为什么只有度数大于该点的点需要改变度数:小于该点的意味着已经被遍历,其度数就是其标记;等于该点的点,显然应当与该点属于同一层,直接保留度数作为标记;对于大于的,根据 \(k\)-degree 的定义,如果不在同一层,当前边贡献的度数显然不符合要求,应当减去,如果应当在同一层,那么由于处理完的度数对应的应当是正确的标记,而当前大于正确标记,应当减去。
详细见下方代码。
#Code
1 |
|