+++
date = "2020-10-05T16:10:00+08:00"
draft = false
tags = []
title = "CSP September 2020"
authors = ["xry111"]
+++

# 第 20 次 CCF 计算机软件能力认证

## [检测点查询](http://118.190.20.162/view.page?gpid=T113)

### 问题重述

给定一个点集 $S \subset \mathbb{R}^2$，再给定一个点 $p \in \mathbb{R}^2$，
求 $S$ 中到 $p$ 的欧几里德距离最小的三个点。

### 题解

签到题，随便搞一下就行了。

## [风险人群筛查](http://118.190.20.162/view.page?gpid=T112)

### 问题重述

给定一个矩形区域 $A \subset \mathbb{R}^2$，再给定若干点列
(点都属于 $\mathbb{R}^2$)，问存在一个点属于该矩形区域的点列个数，
和存在两个相邻的点属于该矩形区域的点列个数。

### 题解

签到题，随便搞一下就行了。

## [点亮数字人生](http://118.190.20.162/view.page?gpid=T111)

### 问题重述

给你一个组合逻辑电路 (包含与、或、非、异或、与非、或非、异或非门)，
保证电路没有反馈环，给定电路的一组输入，求每个逻辑门的输出。

### 题解

超级大模拟，为了简化实现，我们可以将逻辑门抽象成

```c++
struct gate
{
	int rev;
	std::function<int(int, int)> aggr;
};
```

这样的东西，这样就能用统一的逻辑处理各个逻辑门了：用函数对象 `aggr`
把输入组合一下，然后异或一下 `rev`，起到反向的效果。
STL 自带的 `std::bit_or<int>`, `std::bit_and<int>`, 以及 `std::bit_xor<int>`
对象可以直接赋给 `aggr`。

现场比赛的时候忘了异或 `rev`，居然能过样例，结果交上去 $0$ 分自闭了很久……

然后就拓扑排序一下，逆序求每个逻辑门的输出就行了。

## [星际旅行](http://118.190.20.162/view.page?gpid=T110)

### 问题重述

给你个高维球 $B \subset \mathbb{R}^n$，
以及 $m$ 个点 $p_i \in \mathbb{R}^n$，
对于每个点，求出它到其余所有点的，不经过高维球内部的最短路径的长度之和。

### 题解

这题有 $70\\%$ 的数据是 $3$ 维以下，就是
[2018 EC Final 原题](https://codeforces.com/gym/102056/problem/F)，
我当时在赛场上就不会，后来退役了就懒得补题
<del>(导致这次丢掉了校 rk 1)</del>
(rk 1 说他改一个小地方这题就能 95 分，[点击这里可以 orz 他][fc])。
比赛的时候发现是高维，
就考虑先解决 $2$ 维，然后找到正确的平面投影上去做，
结果 $2$ 维的都做了两个半小时：一开始只看到结果是 WA，以为精度爆炸了，
最后才发现可以看每个点的评测结果，发现都是 RE，惊觉 $n$ 和 $m$
的范围看反了……改对以后比赛就剩 $10$ 分钟了，只拿了 $35$ 分。

[fc]: https://www.cnblogs.com/flukehn/p/13689341.html

其实就算能迅速解决 $2$ 维，那个投影估计也会精度爆炸或者超时……

赛后 dalao 们说这个题可以用只需要求点积和距离的方法，这样做多少维都一样。
我们以球心为原点建立坐标系，就是把输入的坐标都减掉球心 $O$ 的坐标，
就不用考虑球的位置了。首先判断从 $A$ 沿直线走到 $B$ 是否不经过球，
先判断直线是否与球相交，就要求直线 $AB$ 到球心的距离。
我们先把 $OB$ 投影到 $AB$ 方向，然后从 $OB$ 中去掉这个投影分量，
就得到 $OB$ 垂直于 $AB$ 的分量，其长度就是 $AB$ 到 $O$ 的距离。
写成公式就是

$$d^2 = |OB|^2 - (\frac{\vec{OB} \cdot \vec{AB}}{|AB|})^2$$

直线不经过球内部就是 $d^2 \geq r^2$，代入上面 $d^2$ 的公式，
移项，得到

$$|AB|^2(|OB|^2 - r^2) \geq (\vec{OB} \cdot \vec{AB})^2$$

这个式子的一个问题是左边可能超过 $64$ 位整数的表示范围，
如果拿 Python 写肯定会超时 (这题常数卡得很死)
<del>，`__int128` 的话不知道评测机资不资瓷</del>。
幸运的是右边根据数据范围判断也就 $10^{16}$ 数量级，
所以我们可以判断一下，如果左边运算会溢出，可以直接认为不等式成立
(<del>垃圾</del>评测机不支持 `__builtin_smulll_overflow` 之类的
[GCC 整数溢出相关内建函数][gcc-builtin-overflow]，只好手动判断)。

[gcc-builtin-overflow]: https://gcc.gnu.org/onlinedocs/gcc-10.2.0/gcc/Integer-Overflow-Builtins.html

然后还有一种直线经过球，但是线段不经过球的情况，
此时 $\angle BAO$ 和 $\angle ABO$ 必有一个是钝角。
根据余弦定理，这就相当于

$$\vec{AB} \cdot \vec{AO} < 0$$

或

$$\vec{BA} \cdot \vec{BO} < 0$$

对于上面这些线段不经过球的情况，$|AB|$ 就是距离。对于剩下的情况，
我们需要在球上做一个弧 $PQ$，使之与 $AP$ 和 $QB$ 都相切。
根据勾股定理，以及切线的性质，很容易求出

$$|AP|^2 = |OA|^2 - r^2$$
$$|QB|^2 = |OB|^2 - r^2$$

开方即可算出路径中的两段直线长度。
剩下的问题就是求出 $PQ$ 的弧长，它就是 $r \cdot \angle{POQ}$。
首先求 $\angle{AOB}$，根据余弦定理它就是

$$cos^{-1} \left( \frac{\vec{OA} \cdot \vec{OB}}{|OA| \cdot |OB|} \right)$$

然后求出 $\angle{AOP}$，它显然是

$$cos^{-1} \left( \frac{r}{|OA|} \right)$$

同理可求出 $\angle{BOQ}$：

$$cos^{-1} \left( \frac{r}{|OB|} \right)$$

即可求出 $\angle{POQ}$：

$$\angle{POQ} = \angle{AOB} - \angle{AOP} - \angle{BOQ}$$

直接用上面这些公式暴力做会超时，因为点积的次数太多了
(我随便写了一下是 $5m^2$ 次)，而本题毒瘤卡常
(补题用的评测姬还比现场赛时候烂)。
注意到在对点 $A$ 和 $B$ 算答案时，涉及的向量都是 $\vec{OA}$ 与
$\vec{OB}$ 的线性组合。根据点积的性质，我们可以将这些向量的点积写成
$\vec{OA} \cdot \vec{OA}$、$\vec{OB} \cdot \vec{OB}$、$\vec{OA} \cdot
\vec{OB}$ 的线性组合。这样我们预处理 $m(m-1) / 2 + m$ 个点积出来就行了，
即可将点积的次数减少到原来的 $1/10$，从而通过本题。

## [密信与计数](http://118.190.20.162/submitlist.page?gpid=T109)

### 问题重述

给你一个根据密文解出明文的 DFA，保证密文和明文是一一对应的。
再给定一个词典，用词典中的单词拼成长度为 $m$ 的明文，要求明文加密后，
对应的密文中不出现词典中的任何单词。求方案数。

DFA 状态数 $n$ 不超过 $50$，$m$ 不超过 $1000$，
单词长度之和 $w$ 不超过 $50$。

### 题解

因为密文和明文是一一对应的，我们可以搞出将明文加密成密文的 DFA。
考虑“密文不出现词典中任何单词”的条件，这就相当于把词典搞成一个
Aho-Corasick 自动机，然后拿密文在上面跑，单词末尾的那些状态是禁入的。
用 $f(m, a, b)$ 表示长度为 $m$，加密 DFA 状态为 $a$，A-C 自动机状态为 $b$
的方案数，再预处理一下每个单词会导致状态从 $(a, b)$ 变到哪里，
然后暴力转移递推即可。时间复杂度是 $\mathcal{O}(m n w^2)$。

<del>为啥现场赛交带 `freopen` 的程序居然能过啊？</del>

<del>$10^8$ 复杂度跑 $93ms$ 真的离谱……</del>
