今天一早上就睡晚了呢0.0,七点半才起床
今天讲的可是分治啊!
分治的原理就是将一个大问题分成两个子问题,子问题再继续分下去。
将简单的子问题处理完以后和并起来得到大问题的答案。
divide−conquer−reduce
Concuerreny
问题
给出n个数,计算它们的和,要求速度快。
如果普通的算法就是一个个加,递归层数4层。
并行算法:解决两个或多个子问题时,同时扔到两个或更多CPU上运行。
运行的时间只是解决一个子问题数量。显然运行速度与递归层数有关。
接下来给出一个数组,求前缀和。看这篇博文0.0
接下来要求设计一个并行的排序算法
双调序列:先单调递增,再单调递减的序列(或反过来)
双调序列排序:左右分成两段,同时从左到右枚举,小的放左边,得到两个双调序列。
并且:左边的双调序列元素小于右侧的双调序列所有元素。
迭代这个过程,长度为2的双调序列=有序。
老师讲这个东西,先画了一个倒着的V,然后把V的右边平移,整个序列是一个X
我们用一个扫描线,从左往右比较上下两个元素,将大的元素放在右,小的放左。
如何得到一个双调序列?我们同样分治
首先将第1/2组从小到大排序,3/4组从大到小排,5/6组从小到大…
这样将相邻两组放在一起就是一个双调序列,然后我们再安装上面的方法。
这样一直排下去,就可以得到一个最大的双调序列。
这个复杂度在并行的情况下应该是
O(log2n)
这个东西叫做排序网络。
主定理- -
然后我们就要开始解题了:
Maximizing Unimodal Arrays双调序列最大值
显然将这个序列差分一下我们就可以二分了233
但是上面是错的,因为如果有相同元素怎么办呢?可能会GG。
三分法!
其实就是比较
a[mid]
和
a[mid+1]
,
<则递归右边,>则递归左边,=则两边都递归。
如果全部数都相等就GG了。
Common Algorithms
目标:找到第
k
大的数。
这个东西和排序有点不一样233,只用找到就行了。
简单的分治算法:选择一个主元,我们递归左边,看左边有多少个数比主元大,设为
若
m<=k
,则在左边,否则在右边,往下一层找即可。
那么问题来了——我们如何找到这个主元?
可以随机!但是这个东西是被很好解决了的:
FindMedian算法:
把数组分成
n5
份,找到每一份的中位数。递归调用中位数算法,找到中位数的中位数。
然后最后用这个中位数作为主元即可。这样分完以后,两个区间最小是原大小的
310
根据主定理
T(n)=T(n5)+T(7n10)+O(n)=O(n)
这玩意的发明人真是牛啊- -(不想列了)
不过这个东西也不好在这写是吧,那么我们还是直接随机吧。
随机炸到
n2
的概率是:
2(n−1)n!
,应该还好吧,这个以后可以推算0.0
Tiling with Triominoes
给定一个
n×n(n=2k)
的矩阵,使用三格的L形骨牌进行覆盖。
问最优方案里,有多少的位置不能被覆盖到,覆盖方案是多少。
首先假定右上角这个格子不能被覆盖,分开这个矩阵,变成了
n2×n2
的矩阵
这样我们在中心的位置放一个L的骨牌,显然左上左下和右下,经过归纳后,都可以放完。
而左上角显然是一个子问题。归纳下去,我们可以得到一个方案,使右上角不覆盖。
将这个结论推广,不论我们选中哪一个格子不被覆盖,我们都可以构造一个方案出来。
Integer Multiplication
高精度乘法,给定两个长度为n的整数,求他们的乘积。
我们可以用FFT,当然更好的是用65536进制,因为进位可以用位运算。
解法就是这样,懒得自己码了。
Similar Trick:Strassen Algorithm
讲了矩阵乘法的优化分块??然后就可以降低复杂度,但是没用。
CF888E
给定n个正整数
ai
和一个模数
m
。找到一个子集
(∑b∈Bb)modm
n<=35,ai,m<=1e9
看起来很难,其实就是爆搜。Meet in the Middle想法
爆搜前一半,爆搜后一半然后合并。
前一半的mod数弄出来以后排序,然后对于后一半的每个mod数在前面二分找就行了。
CF868F
给一个长度为
n
的序列
每一块的代价是“值相同但是位置不同数对个数”,求最小总代价。
n2∗k
的方法很显然了
f(n,k)=min(f(i,k−1)+cost(i+1,k))
显然
f(n,k)
这个函数关于k是单调的
考虑计算两个值
f(i,k)
和
f(j,k)
,
i′
和
j′
是两个决策点(即最后划分点),
显然决策点的位置也是单调的。
考虑i有两个决策点
a
和
如果我们在后面加一个元素,那么扩展的区间的
cost
,
a
显然是不小于
这样子我们可以大力分治。
考虑这一类问题的做法:
如果现在分治区间L M R,分治区间L’ M’ R’,那么已知L’和R’,如何得到M’
事实上我们从L’到R’直接扫一次就行了。
BZOJ3263:三元逆序对(权限题)
给定
n
个三元组
n<=105,Ai,Bi,Ci<=2∗105
偏序题就是每一维用一个数据结构。
三维就是用排序干掉一维,然后CDQ一维,再树状数组一维。
对于这题,我们按照
Ai
排序,考虑对长度分治,每次考虑[L,R]区间内的答案。
递归处理两边,考虑所有跨越中间点的数对,满足i在左区间,j在右区间。
我们从小到大枚举
Bi
,用线段树维护
Ci
。
即每遇到左边的一个
Bi
,我们就将
Ci
这个值放进线段树(维护每个值出现多少次)。
如果遇到右边的一个
Bi
,我们就求一下这棵线段树在
1~Ci
的和。
BZOJ3456贸易线路
求点数为n的简单无向图的个数。简单图:无重边,有自环。
n<=105
戳这里
递推式是
fi=2C2i−∑i−1j=1fj∗Cj−1i−1∗2C2i−j
然后左右两边同除
(n−1)!
fi(i−1)!=2C2i(i−1)!−∑i−1j=1fj∗2C2i−j(j−1)!∗(i−j)!
∑ij=1fj(j−1)!∗2C2i−j(i−j)!=2C2i(i−1)!
那么这是一个卷积的形式,我们令:
A=∑ni=1fi(i−1)!xi
B=∑ni=02C2ii!xi
C=∑ni=12C2i(i−1)!xi
则
A∗B=C
那么
A≡C∗B−1(mod xn+1)
也就是多项式求逆。
ZOJ 3874 Permutation Graph是和上面那题差不多的练习题。
Balkan OI 2007:Mokia
有一个
W×W
的棋盘,每个格子有一个数,初始为0。
维护两种操作:
Add:(x,y)加A,Query(x,y,xx,yy)
询问矩阵和
显然可以用二维线段树做。
当然也可以用CDQ分治做。
实际上我们只用考虑前一半操作对后一半操作的影响。
问题转化为给定点集
(x,y,A)
,查询若干个矩阵内的点。
BZOJ2716天使玩偶
维护一个二维平面,支持如下两种操作:
1.插入一个点
(x,y)
2.询问一个点
(xq,yq)
最近的点,曼哈顿距离
|x,y|<=105,q<=105
首先分治,考虑左半边已经加入的点对右半边的影响。
dis(i,q)=|xi−xq|+|yi−yq|
曼哈顿距离常用套路,分四种情况讨论。
先解决
dis1(i,q)=(xi−xq)+(yi−yq)
求满足:
xi>xq,yi>yq
,且
xi+yi
最小的点。
带权为
xi+yi
的点集,二维区间最值。
上午讲的东西,其实很大程度上是处理一种”依赖关系“
即分治时如何处理前面对后面的影响。
但是上午讲的东西确实超乎了我的认知233
Day8 1.22NIGHT
今天考试很烦啊,T1T2都是秒的题,然后都挂了。
T1老师讲了一个置换群的问题:
一个置换的n次方就代表这个置换进行n次。然而并没有什么用。
倍增乱搞写挂了??
没有判0。
T2写分治,然后又写挂了???但是本地测是过的(上面是RE)
听说
O(qe)
可过。然而不关我事啊
如果没有去掉一个物品的限制,就是一个简单背包。
rqy神犇的思路是对整个区间进行分块,然后每个块背包,再合并。
老师的算法是这样的:
考虑已经有一个背包,那么现在要加入一个新的物体。
复杂度是
O(背包大小的)
,这是十分简单的,但删除是很困难的。
考虑分治算法。
假设现在要求
12
到
34
这区域的dp值,
其他区域的dp值一定使用到这个区域的dp值。
这样我们每次分到一部分时,我们将所有外部的dp值都传到这个部分。
然后再进行合并。
也就是说,对于每个区块,我们将右边的dp值丢到左边去算,
同样将左边的dp丢到右边去算。
我的思路也是分治,同样是将一部分的dp值丢到另一边然后dp。
但是在写法上有点不同。
用单调队列优化以后复杂度应该是
O(nlogn)
的
写挂了很可耻可耻可耻。并没有写挂。
rqy的T3的40pt是这样的:
首先点——边——点的最短距离就是作对称点然后连直线求交。
对于有两条边(即点——边——边——点)的情况也是一样的。
然后就这样水,不断按多边形的顺序跑。
为什么呢?因为跑的路线一定是不交的(如果交一定可以替换为更优的)
但是这样做的时候有个问题,因为可能连起来的直线不在线段上。
正解在这里:
最后的答案一定是从
O
出发,经过若干条边的反射到了一个顶点,
然后再从顶点经过若干条边到另一个顶点,最后再回到原点。
这是两个点之间仅通过边反射的最短距离。
然后我们再用floyd求出任意两个点之间的最短路。
预处理另外两个数组,一个是从原点出发,顺时针反射到某个点的距离,
另一个是从原点出发逆时针反射到某个点的距离,这样线性扫一遍就行了。
计算几何和图论的结合,真好玩啊。
然后我顺便找到了原题,是2012ACM-ICPC-WF的H
WC2007疯狂赛车,也是这样一种题目。
然后继续讲课了
NOI2007货币兑换——分治好题233
有两种操作,
n
天进行货币买卖,第i天可以进行两种操作。
1.花费
2.给定一个比例k,按照市值
ai,bi
把手中
k
的持有的
A
和
给定所有参数问底
n
天后最大持有现金数。
一个很显然的结论是每次都会操作所有的现金/股票
那么我们枚举上一个买入交易日j,第j天花所有钱买入股票,在第i天全部卖掉。
f[i]=maxf[i−1],fa(j)∗a[i]+fb(j)∗b[j]
fb[j]=f[j]a[j]∗r[j]+b[j]
fa[j]=a[j]∗f[j]a[j]∗r[j]+b[j]
对于i,决策j优于k的条件是:
r[j]∗f[j]−r[k]∗f[k]f[j]−f[k]>−b[i]a[i]
但是这个斜率优化的单调性十分的差,我们动态维护这个凸包很难。
平衡树是可以的。但我们考虑更简单的做法(也许吧,我是这么认为的)
其实,我们接下来需要干什么?
1.加点,维护上凸壳
2.用一条固定斜率的线从上往下扫描,回答碰到的第一个点
其中第二个问题等价于在凸包上二分。
考虑CDQ分治,我们先计算出左半边点的dp值,
考虑左半边的dp怎么影响右半边?
——左半边的点可能被右半边的询问线碰到。
操作变成:
给定左半边的点集,构造凸包;
给定左半边的凸包,二分右半边的斜率在左半边的位置。
这题的主要思想是利用CDQ将一些动态的东西转化为静态的。
这样做是
O(nlog2n)
的
考虑进一步优化。
我们比较容易想到的是二分的优化,将右半边的询问斜率进行排序。
然后我们二分后的斜率选择的点就是单调往右的。
再考虑第二步优化,因为我们已经分治了,不妨将下一层的凸包也传上来。
凸包的合并是可以做到
O(n)
用两个指针单调扫的。
这样复杂度就被优化到了
O(nlogn)
这道题的思想是十分重要的,回去一定要好好写一次。
练习题:WF2011,Machine Works
POI2011:METEOR
有
m
个天文台排成一个环形,每个天文台属于
有
k
场流星雨,第
每个国家有陨石需求量
bj
。问每个国家在第几场流星雨后达到陨石需求。
这道题在当初讲整体二分的时候做了0.0
线段树可以维护流星雨的信息,但是怎么统计呢?
我们可以用整体二分233,就是对答案进行分治。
每次询问加上
[L,M]
这段区间的流星,有多少国家满足了。
满足的二分左侧区间,不满足的二分右侧区间。
右侧区间信息怎么维护呢?我们可以用主席树/撤销操作/简单统计。
BZOJ3110 K大数查询
有
n
个vector:
1abc,对于i∈[a,b],vi.push
_
back(c)
2abc,查询Ui∈[a,b]vi集合的第c大元素
第二个操作就是合并
[a,b]
这些区间后的第c大。
这也是经典题目啊,数据结构题QvQ,但哦我们还是要整体二分做!
我们对答案“分治”。那么每次要求的就是对于每一个询问
a,b→ans
。
扫描所有操作,每次询问是否存在至少
C
个数,他们
查询就是查询一段树是不是大于
C
总结一下
分治是一个很有趣的东西,
绝大多部分题,尤其是CDQ分治和整体二分,
将动态的问题变成了一些静态的东西。
最后听说是一道娱乐题:K近邻相交查询
给定
(x,y,k)
,询问距离x最近的k个点和距离y最近的k个点是否有交集
假定对于每个点没有两个点和它距离相同。
n,k<=105,ai<=109
又是rqy讲的qwq
显然距离每个点前k近的点是一段连续的区间。
二分距离x第k近的点距离是多少,然后我们再二分一次,距离x不超过v的点距离是多少。
这个做法是
O(nlog2n)
的
二分以每个点为中心往外扩
k
个,那么显然我们要看看x左延和右延的哪边距离大
balabala
这个做法是
然后还可以拓展下,变成二维询问曼哈顿距离的K近邻相交查询
做法是一样的。
但是我们可以考虑旋转45度233
对于每个询问我们就可以转化为旋转45度的矩形有没有交。
今天真的是很不爽啊
做题量太少的后果。
明天要开始学数学了,十分慌张QAQ