数学:计算几何二维平面基础
Preface
学校计算几何专题刚刚好讲完,放一下计算几何用到的板子;
Content
Problem
前摇:常见常数,误差比较
由于计算几何常常带有浮点类型的运算经常等号判定会有偏差,所以这里采用误差比较的方式判定相等;
1 | typedef double db; |
常见的二维几何对象
点模板
1 | template<typename T>struct point { // 点类 |
直线模板
1 | template<typename T> struct line { |
线段模板
1 | template<typename T> struct segment { |
普通多边形模板
1 | template<typename T> struct polygon { |
圆模板
1 | template<typename T> struct circle { |
点与点之间(向量与向量)之间的关系
极角排序
利用库函数极角排序
先将所有点的极角算出来,然后按极角的值进行排序,不是将 atan2(y,x) 函数写在排序的比较函数里;
注意,atan2(double y,double x)
函数返回的是原点至点 $$( x , y )$$的方位角,即与与x轴正半轴的夹角,范围是$$(-\pi,\pi \ ]$$;
在极角排序中如果返回的极角值相同则按与原点距离升序;
相对于外积排序这个速度更快,但是可能有精度问题,如果一直$$WA$$就考虑把这个换掉;
注意:使用atan2()
应该先传y
再传x
;
1 | bool cmp(point a, point b) { |
排序结果如图所示: $$A-B-C-D-E-F-G-H-I$$ ;
利用外积极角排序
一般利用外积可能会出现转一圈小于自己的情况,因为右手定则只能判断某两个的相对情况,所以我们利用象限作为排序的第一关键字就可以解决这个问题了;
相对于前者精度更优但是速度一般,如果题目有容错就不要写这个了;
1 |
点集的凸包
1 | void Convex(vector<point<db>>&Convex_A,vector<point<db>>A){ |
点定位($$PIP$$问题)
点定位问题大致分为三种方法:外积二分法,光线投影法,$$Winding \ Number$$(回转数法);
其中,外积二分基于凸多边形的三角剖分,常与点集的凸包联系起来,通过确定极点并二分极角序确定点的位置;光线投影法属于扫描线算法的一种;回转数法适用于任意多边形,但是其经典形式存在着一定的精度问题;
平面最近点对
判断点与直线(射线,线段)的位置关系
$$ToLeftTest$$ : 判断点是否在直线的左侧
在上面的点模板和直线模板中也有用到,所以也可以引用上面的结构体内联的函数;
$$ToLeftTest$$ 就是利用向量的外积判断正负,外积为正说明在右手转过的角小于 $$\pi$$ ,点在直线左侧,大于$$\pi$$ 则说明点在直线右侧,如果等于零,在直线两点和该点构成的三角形面积为零,因此在直线上;
1 | int to_left(const line<double> &l, const point<double> &p) { |
点到直线距离
在直线上取两点可以转化为三角形的高,利用外积求出面积,利用距离公式求出底;
1 | double dist(const line<double> &l, const point<double> &p) { |
点到直线的投影点
利用内积关于投影的性质,将向量运算转化为模运算;
1 | point<double> projection(const line<double> &l, const point<double> &p) { |
两直线交点
1 | point<double> intersection(const line<double> &a, const line<double> &b) { |
判断直线与直线(射线、线段)的关系
半平面以及半平面交
判断圆与点、线、圆的关系
两圆的位置关系:相等,相离,内含,外切,内切,相交;
倒不如说考虑问题应该按照这个顺序想,因为前面的特殊情况往往蕴含着不一样的答案;
1 | //两圆关系:5相离,4外切,3相交,2内切,1内含 |
圆与圆的面积交问题
很明显在最朴素相交情况下,两圆面积交可以表示为两个弓形的面积之和,这样我们就转化为容斥原理求解即可;
注意我们这里使用$$Heron$$公式求解三角形的面积,尽量绕开对反三角的求解;
1 | int relationcircle(circle k1,circle k2){ |
圆与多边形面积交问题
1 | //#pragma GCC optimize(2) |
圆的面积并问题
圆外一点对圆的切线问题
两圆的公切线问题
1 | int get_tangents(circle A, circle B, pnode *a, pnode *b){ |
圆与直线的反演变换
最小圆覆盖
$$Ἀπολλώνιος$$圆
凸集问题
动态凸包
Minkowski Sum
1 | void Minkowski(vector<point<db>>&s,vector<point<db>> a,vector<point<db>> b){ |
基本几何量的计算
三角形的有向面积(逆时针为正)
我们知道,两个向量的外积表示某个平行四边形的面积,那么我们只需要计算三角形两条边的向量外积的一半就是这个三角形的有向面积,实际面积即为有向距离的绝对值;
1 | double triarea(Point p, Point q, Point s){ |
三角形$$Heron$$公式(三个顶点不知情况下)
Pick定理与整点问题
计算几何与数据结构
KD-Tree
Example
Remark
1 |
\[数学\]逆序对相关:归并排序,树状数组,以及简单群论
Preface
整理题解的时候正好路过的知识黑洞,填补一手;
Content
Problem
如何将大范围的数据找到它们的排名数组,也即离散化;
使用线段树、树状数组解决“单点修改区间查询“,“区间修改单点查询”,”区间修改区间查询“,”区间最值“等问题
给定长度为$$n$$的排列,找到这个排列的逆序对个数;
Solution
离散化
对每个数和其下标捆绑排序 ,方便找到原来的位置;
1 | vector<long long>a; |
$$Segment\ Tree$$
综述
- 线段树是一种二叉平衡树,支持维护区间和,区间最值,区间异或和等多种信息,要求对维护的运算存在结合律和存在幺元;
- 线段树也支持对单点修改,区间修改,区间查询,同时也支持对线段树之间的合并,删除点等操作,能够处理比线段数组更加复杂的问题;
含义
1 | template<typename T,auto op,auto e,typename F, |
$$Finwick\ Tree$$
综述
对于输入一个长度为$$n$$的数组$$a$$,其数状数组也是一个长度为$$n$$的数组$$Bitr$$,数组中的每个元素维护着一个区间的区间和;
数状数组解决的问题下标需要从1开始,确保计算$lowbit$能到正确的位置;
树状数组解决的是一类动态前缀和问题,能够在$$O(logn)$$实现数组的单点修改与区间查询,而朴素算法只能在$$O(1)$$内解决修改(查询)而在$$O(n)$$内解决查询(修改);
区间最大公约数,区间最值可移步线段树;
含义
$$Bitr [i]$$和其维护区间$$[l_i,r_i]$$的关系
- 树状数组的下标就是区间的右端点,换言之,$$r_i=i$$;
- $$Bitr[i]$$维护的区间长度等于$$i$$的二进制表示中最右端的$$1$$所代表的$$2$$的幂次,换言之,$$r_i-l_i+1=lowbit(i)=i&(-i)$$
- 树状数组的第$$i$$位维护的区间为$$[i-(i&(-i))+1,i]$$,可以理解为每次线段树中二分的两个区间左边的那一个;
- $$Bitr [i]$$维护区间$$[i-(i&(-i))+1,i]$$的和,换言之,$$Bitr [i]=\sum_{t=i-lowbit(i)+1}^{i}a[t]$$
$$Bitr$$如何求解区间和$$\sum_{l\le t\le r}a[t]$$
- $$\sum_{l\le t\le r}a[t]=\sum_{i=1}^{r}a[i]-\sum_{i=1}^{l-1}a[i]$$转化为前缀和之差来求解
- 将$$[1,x]$$递归地拆分成若干个区间$$[1,x]=[1,x-lowbit(x)]\cup[x-lowbit(x)+1,x],x\gets x-lowbit(x)\ when\ x\ge1$$;
更新$$a[t]$$如何修改$$Bitr$$
- 我们需要知道$$t$$包含在哪些区间之内
- 初始要更新的区间为$$[t-lowbit(t)+1,t]$$
- 假设当前最新已经更新的区间为$$[l,r]$$,下一步要更新的区间为$$[u,v]$$,在线段树中二分区间左边的为$$[l,r]$$,那右边的那个区间(实际在树状数组中并不存在)左端点为$$r+1$$.长度为$$lowbit(r)$$,那么要更新的区间就是左右两个区间的并集,$$v-(r+1)+1=lowbir(r),v=r+lowbit(r)$$;
- 我们递归地更新$$t\gets t+lowbit(t) \ when \ t\le n$$
实现区间修改和原数组的单点查询
- 引入差分数组:$$a[i]=\sum_{j=1}^{i}d[j],d[i]=\left{\begin{matrix}
a[i] & i=1\
a[i]-a[i-1] &i>1
\end{matrix}\right.$$ - 区间$$[l,r]$$内增加$$val$$,在差分数组中表现只有第$$l,r+1$$项发生变化,分别$$+val,-val$$;
- 单点查询$$pos$$相当于求差分数组的前缀和,由于我们一直维护的都是差分数组所以原数组未更新;
- 区间查询同样可以转化为前缀和之差,而前缀和$$\sum_{i=1}^{t}a[i]=(t+1)\sum_{i=1}^{t}d[i]-\sum_{i=1}^{t}id[i]$$,维护两个树状数组即可;
- 引入差分数组:$$a[i]=\sum_{j=1}^{i}d[j],d[i]=\left{\begin{matrix}
代码实现
1 | //注意这是C17的语言特性 |
逆序对
逆序对当然很容易用树状数组来求出,先将数据离散化,统计
1 | #define local |
Example
Remark
\[数学\]二进制位运算相关性质
图论:最短路相关算法-Dijstra,SPFA and Floyd
Preface
暑假集训用到了最短路的板子但之前的板子没存下来,在加上之前也看的不是很明白,故这里放点笔记;
部分板子目前还未测试过,谨慎使用$$(2023.7.15)$$;
Content
Problem
用链式前向星方式存储一个有权图$$G(V:E)$$,找到点 $$ i,j $$ 之间权值最小的路径长度;
注意:有时题目中的图其实就是树,而树的路径长度用 $$dfs$$ 就好了不需要用这个;
通常包括$$\ Dijstra 算法,SPFA \ and \ Floyd算法$$,其中小心SPFA被卡爆();
Solution
$$Dijstra$$ 算法
Notice:
该算法只能处理边正权图的单源最短路问题,但是它的效率十分优秀,一般采用带小根堆优先队列优化
($$ZKW$$线段树!$$Fibonnaci$$堆!);时间复杂度为$$O((|V|+|E|)log|V|)$$;
但这个复杂度放在稠密图里可能会被$%$$$hack$$,需要换成邻接矩阵的方式存图,复杂度为$$O(|V|^2)$$,
遇到了再补吧;
Proof:
- 对于每个点$$v$$均维护一个「当前最短距离数组」$$dis[v]$$ 以及「访问数组」$$vis[v] $$,首先将$$dis[u]$$初始化为0,将其他点的距离初始化为无穷,并将所有点初始化为未访问的。记$$e[i]:u-v$$的边权为 $$e[i].w$$。然后进行以下步骤:
- 从所有未访问的点中,找出当前距离最小的,并将其标记为已访问的;
- 调整所有出边连接但尚未访问的点,转移状态;
- 重复1和2步骤,直到所有点都已访问,因为所有边权权值均为正的,所以用之后访问的结点是不能更新已经访问过的结点的(因为贪婪算法下若可以更新的话必定在还在之前就已经更新过了);
- 可以用
pre[y]=i
维护最短的路径(点边关系);
Template:
1 | #define local |
$$Floyd$$ 算法
Notice
- $$Floyd$$ 算法是基于最朴素的动态规划的思想,求出全局上的任意两点的最短路问题,可以处理负边权的情况;
- 我们知道对于图$$G$$ 中存在负权值环的话,沿着这个环一直前进是不会得到最短路的,$$Floyd$$ 算法也无法处理这类的问题;
- 在一些加了若干限制的最短路问题中,往往都要回归到朴素的$$Floyd$$算法来;
Proof
考虑对每个$$k$$值,「$$i$$到$$j$$ 的路径只允许经过标号不大于$$k$$的点」中最短路径长度$$dis[i][j]$$,明显$$k=n$$是我们所求问题的答案;
初始$$k=0$$,所欲值都是正无穷;
递归的说,对于已有的$$k-1 $$允许的最短路径,新的最短路径要么经过$$k$$,要么维持不变;
状态转移方程为
$$
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]),for\ all\ (i,j)s
$$时间复杂度为$$O(|V|^3)$$;
Template:
1 | //#pragma GCC optimize(2) |
$$SPFA$$ 算法
Notice:
- 对于一般的带权图,不同于正权图的$$relax$$操作每次取出最小的$$dis$$来进行下一步,边权有负数是不保证正确性的,所以我们维护一个普通的队列就行了,这也是$$SPFA$$经常得卡爆的地方,构造数据可以欺骗算法进入冗余的分支,使得队列的进出和$$relax$$多了很多不必要的操作;
- 这个算法本质是$$Bellmon-Ford$$算法的局部优化,复杂度为$$O(|V||E|)$$;
- 所以说没必要仔细解释$$Bellmon$$算法对全局的$$n-1$$次$$relax$$操作了,卡$$SPFA$$的一定卡$$Bellmon$$;
- (保证输入是随机数据)慎用,不要偷懒!
因为它已经死了;
Template:
1 | //#pragma GCC optimize(2) |
Example
暂时没有;
Remark
还是多整理一些模板少打比赛吧,太坐牢了,怎么写那么快的啊;
学校的专题等一波题解发下来吧,真的不知道里面是什么毒瘤数据;
UESTC 2023 Summer Training#4 Div.2暑假集训
Preface
作为暑假集训的第一篇博客,为什么是#4呢?因为终于写出了免于爆零了 ,感慨良多,虽然在赛场上一直卡G
(英语不好读不懂题),但明显感觉到卷子的难度正在适应我的水平,而且几何和字符串专题根本做不动,借着这个契机开始整理笔记(感觉学习曲线过于陡峭了);
赛后补题只补了六道,留坑记录一下;
A:cf245D 1500 二进制
B:cf1557B 1100 排序
C:cf1338B 1800 构造
D:cf1430E 1900 转换后求逆序对
E:cf915F 2400(虚高) 并查集
F:cf1668C 1300 暴力,贪心
G:cf176D 2100 有些技巧的DP
H:cf448D 1800 二分答案
#A. Restoring Table
题源:CF245D
题意:
给定对称矩阵和规模n
,满足如下关系,要求还原数组a
$$
b_{ij}=\left{\begin{matrix}-1
& i=j\a_{i} & a_{j}
& i \ne j
\end{matrix}\right.
$$
思路:
- 对每个
b
做二进制拆分不难发现还原a
只需要对相应下标做按位且运算即可,而且确实如题目所说答案并不唯一; - 本场签到题;
AC code:
1 | #include<bits/stdc++.h> |
#B. Moamen and k-subarrays
题源:CF1557B
题意:
询问能否将长度为n
的数组分成k
块,重新排列得到顺序;
思路:
- 我们考察可以把原数组的若干个数绑定在一起的块,若能重新排列得到顺序,那么块里的数必然在顺序排列中也是相连的;
- 如果原数组这样分块后存在(可能不到)
k
个块,那么把一些好的块打散肯定也是符合题意的; - 考虑原地排序还原下标,依次遍历检查相邻两数能否组块,采用
pair
输入; - 也是签到题,但是没看榜后面的时间
all in G
题了(所以这场没有到);
AC code:
1 | #include<bits/stdc++.h> |
#C. Edge Weight Assignment
题源:CF1338B
题意:
没读;
思路:
没有;
#D. String Reversal
题源:CF1430E
题意:
将字符串对换成其反串最少的操作次数;
思路:
- 开始陷入误区尝试证明最少的次数是正串和反串逆序对差的绝对值,
结果样例都跑不过,换思路; - 直接模拟对换过程,猜想不做故意浪费的操作
换回来又换回去即为最小次数; - 建立一个目标数组,即每个字符最终应该到达的下标位置,考虑到有重复字符要做到某种意义的最优应使得对于正串某个字符第
i
个目标是反串相同字符的第i
个; - 对目标数组最终变为自然排列,因而最少的次数就是这个字符串的逆序对个数,归并排序即可;
- 这个转化初见,有些巧妙的,留坑归并排序、树状数组的模板整理于此(做这个题时也没学过归并排序,线段树写不下去了思路还是错的就摘了个板子过来还没仔细看看);
- 复杂度
O(n logn)
;
AC code:
1 | #include<bits/stdc++.h> |
#E. Imbalance Value of a Tree
题源:CF915F
题意:
给定树T
以及树上结点赋点权a[i]
,定义I(i,j)
为i
与j
之间的路径经过点最大值与最小值之差,计算
$$
\sum_{i=1}^{n} \sum_{j=i}^{n} I(i,j)
$$
思路:
开始采用树上倍增找到
lca
的做法,但是MLE
了,毕竟确实开了很大的数组,而且也感觉会跑不过;但还是见的少了,本题是
DSU
模板题;首先观察到在树上路径操作,并且经过的边显然只取决于它的端点,考虑将点权转化为边权(
常见套路),在考虑最大值问题时为两端点最大值,反之亦然;转化表达式,维护各个最大值和最小值贡献的次数,考虑最大值这一边的问腿,
$$
\sum_{i=1}^{n} \sum_{j=i}^{n} I(i,j)=\sum_{i=1}^{n} \sum_{j=i}^{n} \max_{e\in i\longrightarrow j} w_1(e)-\sum_{i=1}^{n} \sum_{j=i}^{n} \min_{e\in i\longrightarrow j} w_2(e)
=\sum_{e\in T} w_1(e)p_1(e)-\sum_{e\in T} w_2(e)p_2(e)
$$p1(e)
可以看作由e
的权值决定这样路径i~j
的条数,很明显权值越小能够能决定的路径数越少;将权值原地排序,用
DSU
维护边全局信息,两边相连则加入DSU
中,对树做路径压缩的DSU
总会转化为星状图,所以每次更新将贡献次数传递给父亲并更新DSU
的状态就好了,并且开始做的点权转化使得每条边至少可以控制自己两个端点之间的路径(就是自己),所以初始化为1
;要加上输入优化,
不知道为什么跑出来差别那么大看起来真的输入了很多很多数来卡人,复杂度应该是略大于O(n)
,这是由于路径压缩的DSU
复杂度是近似常数,所以数据膨胀的飞起;
AC code:
1 | #include<bits/stdc++.h> |
p.s.
看的出来对并查集也不是很熟悉,留个坑整理一下;
#F. Make it Increasing
题源:CF1668C
题意:
给定数组a
,零初始化b
,找到a[i]b[i]
严格递增的数组b
,最小化
$$
\sum_{i=1}^{n}|b[i]|
$$
思路:
- 想法是活用负数,暴力找到一点置为0,左边为负数右边为正数;
- 于是
brute force
枚举置零的位置,只需要比较当前最优方案即可; - 最优方案是贪心的使得
b[i]a[i]
从0
位置开始看变化的缓慢一点,至少保证能递增的最低限度; - 试图优化了但直接
WA
了一发,因为找不到确切的i
,枚举变化陡峭的点也是在O(n)
的,比如1 1 4 5 1 4 重复; - 复杂度$$ O(n^2) $$
AC code:
1 | #include<bits/stdc++.h> |
#G. Hyper String
题源:CF176D
题意:
没看
思路:
没有
#H. Multiplication Table
题源:CF448D
题意:
找到m·n
的乘法表中非去重非递减第k
大的数;
思路:
- 比赛的时候感觉
html
彻底崩了,读了好几遍题目没有读懂里面几个单词的意思,一直以为是要去重的所以连样例也没读懂但好多人都过了也不懂,反正当时就懵逼的一批这也是掉大分的罪魁祸首(); 感觉去重的话可能是个超级大容斥加筛法;- 赛后发题源秒懂这就是二分的签到题();
- 对每次折半询问一个数,在表里算出不大于它的数的个数;
- 复杂度
O(nlogmn)
,让我们一起学习二分吧;
AC code:
1 | #include<bits/stdc++.h> |
Remark
- 大概率是过不了线了感觉学的东西已经应付不过来了,如果没有进入的话正好留点时间给自己整理一下;
没爆零就是;win
UESTC 2023 Summer Training#5 Div.2暑假集训
Preface
第五场,2023.7.14的比赛,好像这次歪榜比较严重,写完$$A$$就没时间看$$B$$了,但是$$B$$好像挺简单的;
#A.
题源:Gym - 103828A
题意:
给定两个数列$$A,G$$,要求构造字典序最小的数列$$P$$: 若$$i \ne j,A_i\ge A_j,G_i=G_j$$,则$$P_i>P_j$$,若不存在输出$$-1$$;
思路:
一眼由于自反性$$A_i\ge A_j$$的等号取不到,若存在$$A_i=A_j,G_i=G_j,i\ne j$$,则输出$$-1$$;
将$$A$$中的数按照$$G_i$$的值作为一个桶放进去,再桶内排出大小,由于要求$$P$$的字典序最小,所以要根据下标使得$$P_i$$取尽量小的值,我们设置一个时间戳数组,顺序遍历$$A$$,若当前$$A_i$$时间戳为$$0$$(未访问过),那么进入$$A_i$$所在的桶,遍历桶中的尚未赋上时间戳的数,给他们赋上相应的时间戳,直到在桶中找到$$A_i$$的值停止,给桶打上相应标记方便下次进入直接从标记开始;
AC code:
1 | #define local |
#B.
题源:Gym - 103828D
题意:
对字符串做$$n$$次$$ctrl+A+C+V$$,找到字典序第$$k$$位;
思路:
在比赛的时候想找到二进制下的规律结果什么也没看出来;
后来的某一天在看时,发现直接用归纳的角度来看,标签是指$$-Ci$$,如果我们已经有了$$n-1$$情况的表,那么可以将$$n$$ 情况的表分成三个部分,比如说样例$$(X,2)$$所有可能的$$k$$值构成的表(假设$$X=Acm2022-23,Y=Acm2022-23-C0000$$):
- 第一部分只有一个串,即为原串$$X$$
- 第二部分有$$2^{n-1}$$个串,可以看成$$(Y,n-1)$$ 的表直接粘贴,即$$Y,Y-C0000$$
- 第三部分有$$2^{n-1}-1$$个串,可以看成在$$(X,n-1)$$的表中除去原串,复制过后,每个串的第一个标签递增$$1$$,即$$X-C0000$$递增$$1$$后,为$$X-C0001$$
注意一些细节,标签递增要把标签$$-Ci$$那个数$$i$$给取出来,以及直接比较$$k\le 1+2^{n-1}$$会爆long long
;
AC code:
1 | //#pragma GCC optimize(2) |
#C.
题源:Gym - 103828I
题意:
思路:
AC code:
1 |
#D. Even Adjacent Product
题源:Gym - 103828J
题意:
输出一个长度为$$n$$的置换,使得相邻两数乘积为偶数
思路:
输出$$1$$到$$n$$即可;
AC code:
1 | //#pragma GCC optimize(2) |
#E.
题源:Gym - 103828H
题意:
组合计数,输出正多边形中筝形的个数;
思路:
首先,如果边数为奇数,是不存在筝形的;
其次,对于偶数的情况,我们按照对角线进行分类,与这条对角线垂直的对角线的两个端点,和原对角线两个端点构成筝形;
排除重复的情况(指筝形变成正方形),此时两条对角线长度相等;
AC code:
1 | #define local |
#F.
题源:Gym - 104345L
题意:
思路:
AC code:
1 |
#G.
题源:Gym - 103828L
题意:
思路:
AC code:
1 |
#H. Drazil and Tiles
题源:CF515D
题意:
对于$$m\times n$$的表格,有些格子挖去,能否用唯一的方案用多米诺骨牌覆盖;
思路:
$$Hall$$匹配;
AC code:
1 |
#I.
题源:Gym - 104114G
题意:
给定一系列在水平线上咬合的齿轮坐标和所有的齿轮半径,找到从左到右各个齿轮的半径;
思路:
并没有什么好的办法,暴力搜索一个齿轮可能的半径,用这个数据反解出其他齿轮的半径,再检验是否符合要求;
用到的剪枝思路:
为了使得搜索的次数尽可能少,找到相邻两个齿轮半径之和最小的两个齿轮,搜索一个齿轮可能的半径,它一定比半径之和要小;
在检验答案的过程中,如果得出的半径明显不符合要求(小于可能的半径最小值或者大于可能的半径最大值),直接不考虑了;
AC code:
1 | #define local |
Remark
![](https://kytolly-1318202921.cos.ap-chengdu.myqcloud.com/202307310229931.png