一些最值问题
- 最小不能生成元素
- 几何
- 概率和期望
- 字典序
- 带关联最大和问题
- 图论
- 最长公共子序列问题
- 最小总和
- 给定和最大乘积
- 置换与交换
- 最小差值问题
- 区间减法问题
- 树上最小遍历距离
- 向量
- 最大线段分组交
- 矩阵最少上色
- 最大和交错数列
- 最小字典序矩阵
- 多重排列最大相同元素距离
- 最少逆序对
最小不能生成元素
题目1:给定n个面值为a1,…,an的硬币,其中1≤ai≤109。现在要求找到最小的非负价格p,满足不存在一个硬币子集,它们的和为p。
提供一个问题。
我们可以用动态规划来计算,但是这样会导致状态数爆炸。
我们可以重新理解这个问题。我们驾车在x轴上行驶,初始的时候我们位于0处,并向正方向行驶。对于硬币ai,我们可以理解是一桶位于ai−1处的汽油,它可以让我们额外行驶ai距离。问最远我们可以将车停在哪里(我们的实际答案还需要额外加1)。
这当然是一个贪心问题,我们可以O(n)求解。
题目2:给定n个面值为a1,…,an的硬币,其中1≤ai≤109。之后给定q个请求,请求分两类:
- 每个请求给定一个区间l≤r,要求仅考虑硬币al,al+1,…,ar,要求找到最小的非负价格p,满足不存在一个硬币子集,它们的和为p。
- 将第i个硬币的面额修改为x,其中1≤x≤109。
其中1≤n,q≤105。
提供一道题目。
借助题目1的思路,我们可以把问题看成加油站问题。
由于涉及区间查询和单点修改,我们果断将结构丢到线段树上维护。每个线段树结点维护区间中的所有汽油信息。每个汽油有两个属性,所在位置和油量。
之后我们考虑如何实现上推操作(合并两个孩子的状态)。可以发现上推会导致顶点的汽油数爆炸,越往上单点维护的汽油数越多,上推难度也越大。但是可以注意到如果有一桶汽油,左边的所有汽油油量总和超过汽油所在的位置,那么这个汽油的油量可以直接加入到它左边的汽油中去,并且删除右边汽油。因此一个结点实际维护的汽油,一定满足左边的油量前缀和小于这个汽油的坐标,此时我们每次在尾部增加一桶汽油,那么总的油量会至少翻倍,而汽油的坐标不超过109,因此最多一个结点需要维护log2109的汽油,约30个元素。
因此总的时间复杂度为O((n+q)log2nlog2109)。
几何
到中心最小距离问题
题目1:给定n个二维平面点(x1,y1),…,(xn,yn),要求找到一个中心(a,b),满足所有点到中心的曼哈顿距离之和最小。
由于是曼哈顿距离,所有不同的维度可以分别计算。
现在的问题变成了有n个数值x1,…,xn,找一个中心值a,满足∑ni=1|xi−a|最小。这里我们直接取a为x的中间数即可。
题目2:在一个无穷大的棋盘上,给定n个网格点(x1,y1),…,(xn,yn),这些网格内放置了棋子。之后我们要将这些网格点移到到一块,即所有棋子占用的行恰好为连续的n行,而所有棋子占据的列恰好是连续的n列。每次移动棋子,可以将其移动到有公共边的网格中。问最少需要的移动次数。
这里我们可以把行和列拆开来计算。现在的问题是存在n个整数x1,…,xn,很显然在移动的时候不会改变这些整数的顺序。要求找一个整数a(与x1−1对齐),满足∑ni=1|xi−(a+i)|最小。我们修正一下公式可以发现为∑ni=1|(xi−i)−a|,因此a实际上是x1−1,x2−2,…,xn−n的中位数。
提供一道题目SRM 795 div1的第二题。
题目3:给定n个二维平面点(x1,y1),…,(xn,yn),要求找到一个中心(a,b),满足所有点到中心的2范数距离之和J(a,b)=∑ni=1(a−xi)2+(b−yi)2最小。
考虑偏导数:
∂∂aJ(a,b)=2n∑i=1(a−xi)由于极值点在偏导数为0的时候取到,因此有:
2n∑i=1(a−xi)=0⇒na=n∑i=1xi⇒a=1nn∑i=1xi同样有b=∑ni=1yi。
题目4:给定n个二维平面点(x1,y1),…,(xn,yn),要求找到一个中心(a,b),满足所有点到中心的欧几里德距离之和J(a,b)=∑ni=1√(a−xi)2+(b−yi)2最小。
可以证明函数J是凸函数,因此可以上三分套三分,梯度下降,模拟退火啥的。
凸函数的证明。
一维点的匹配问题
题目1:给定序列a1,…,an以及b1,…bm。要求找到一个1到m的置换p1,…,pm。记置换的权重为∑ni=1|ai−bpi|。要求找到置换的权重的最小值。其中1≤n≤m≤5000。
不妨认为a和b序列都已经预先排好序了,那么最优解的情况下一定有p1<p2<…<pn。
因此我们可以记dp(i,j)表示从1,…,i中选择j个作为p1,…,pj,带来的最小权重。那么每个状态只需要考虑两种转移即可。总的时间复杂度为O(nm+nlog2n+mlog2m)。
题目2:给定序列a1,…,an以及b1,…bm。要求找到一个1到m的置换p1,…,pm。记置换的权重为∑ni=1|ai−bpi|。要求找到置换的权重的最大值。其中1≤n≤m≤106。
不妨认为a和b序列都已经预先排好序了。
考虑ai和ai+1,它们匹配的值为bpi和bpi+1。很显然如果ai≥bpi且ai+1≤bpi+1,那么我们交换pi和pi+1可以得到更大的一个权重。因此我们可以得出ai≥bpi⇒ai+1≥bpi+1。
根据上面的结论,我们可以找到一个点k,对于i≥k,都有ai≥bpi,同时对于i<k,都有ai≤bpi。此时答案为∑k−1i=1(bpi−ai)+∑ni=k(ai−bpi)。这时候我们要最大化p1,…,pk−1,同时最小化pk,…,pn,可以发现两者可以同时满足,此时若i<k,则pi=m+1−i,若i≥k,则pi=i−k+1。
通过预处理前缀和,我们可以O(n+m)遍历所有可能的k。总的时间复杂度为O(nlog2n+mlog2m)。
概率和期望
概率
题目1:给定n个独立的随机变量x1,…,xn,其中第i个随机变量从1到ti中均匀随机取值,其中m=∑ni=1ti。要求对于1≤i≤m,求出maxnj=1xj=i的概率。其中1≤n,m≤106。
一种直接的想法是用DP的方式,但是这样会导致时间复杂度变成O(nm)。
一般遇到min-max概率或期望计算,都需要借助前缀和的技术。这里我们记f(i)表示最大值不超过i的概率。可以发现f(i)=∏tj≥iitj。之后通过差分就可以得到P(maxnj=1xj=i)=f(i)−f(i−1)。
上面过程的时间复杂度为O(nlog2n+m)。
收集问题
题目1:一个游戏总共有n个奖章。每完成一局游戏,会固定得到一个奖章,其中得到的是第i个奖章的概率是pi,其中∑ni=1pi=1且pi>0。要求计算搜集到所有奖章的期望游戏局数。其中1≤n≤25。
这里我们可以使用min-max容斥。记xi表示第i个奖章的获得时间。则
E[nmaxi=1xi]=E[∑I⊆N(−1)|I|−1mini∈Ixi]=∑I⊆N(−1)|I|−1E[mini∈Ixi]其中mini∈Ixi表示多个奖章中最早获得奖章的获得时间。这等价于抛掷一枚不均匀硬币,第一次出现正面的时间,其等于1/p,其中p是出现正面的概率。因此化简可以得到:
∑I⊆N(−1)|I|−1E[mini∈Ixi]=∑I⊆N(−1)|I|−11∑i∈Ipi上面的公式可以O(2n)解决。
字典序
字符串合并
题目1:给定两个字符串A,B。以及一个空字符串C。之后我们可以不断的从A,B中选择一个非空字符串,将其头部元素删除并加入到C尾部。重复这个流程,直到字符串A,B都为空。要让C的字典序最大。
提供一道题目。
首先说做法。每次比较A和B,将其中字典序较大的字符串的头部元素删除追加到我们的C中即可。这个过程,可以直接通过暴力比较O(nm),或者一些复杂的后缀处理技术,比如后缀数组来提速,时间复杂度为O(n+m)。
下面说明一下这样做为什么是对的。
首先我们可以在两个字符串尾部都加上一个代表无穷小的哨兵字符,这样就可以避免讨论两个字符串一者是另外一者的前缀这种情况(但是两个字符串还是可能相同的)。很显然最后构建的字符串的尾部两个元素就是我们加入的哨兵字符。
记inv(Ai)表示Ai在最终结果(C)中的下标。
如果A=B,很显然从哪个字符串头部删除都不会影响结果。下面认为A>B。我们记t为第一个不同的位置,即A1=B1,…,At−1=Bt−1,但是At>Bt。
首先我们证明如果最终结果最优,则At一定出现在Bt之前。如果不是,记L=inv(Bt),此时Bt在前。则对于所有1≤i≤t,我们将最终结果中Ai与Bi位置互换。此时可能发现换位后Bi出现在Bt+1之后的情况,我们可以将所有B占据的位置上的元素按照它们的下标重新排个序即可。可以发现修改后C的长度为L−1的前缀是不会改变的,而第L个元素从Bt变成了At,增大了,这说明了我们最终结果并不是最优的,与前提相悖。因此inv(At)<inv(Bt)。
之后我们证明至少存在一种最优结果,满足inv(A1)=1,即A头部元素放在C的最前。证明方法也是构造性的,从任意一个最优解出发,如果此时最优解的头部元素是B1而不是A1,我们记j表示最大的下标,满足inv(Bj)<inv(At)。之后我们将A1与B1的位置互换。可以发现我们的最优解值是不会变的。但是这时候可能出现冲突,即inv(B1)>inv(B2),B1被交换到B2之后了。我们可以继续让B2与A2交换位置。重复上面这个流程直到结束。很显然这个流程是会结束的,因为Bj不管是否交换都不会出现在Bj+1之后。而这个过程由于不会改变解的值,因此修改后的解依旧最优,且此时inv(A1)=1。
因此根据上面的结果就可以放心大胆的使用贪心算法,因为无论如何都可以通过后续的手段将结果最优化。
题目2:给定n个字符串s1,…,sn,要求将它们重新排列拼接,要求拼接得到的字符串字典序最大。
提供一道题目。
考虑两个结果中相邻的字符串A,B。如果字符串A排在B之前,那么一定有AB≤BA,否则交换后可以得到更优的结果。
而考虑到AB≤BA等价于A+≤B+,因此保证偏序关系的成立,因此我们可以使用比较算法。
时间复杂度为O(nmlog2n)。
带关联最大和问题
题目1:给定一个序列a1,…,an,要求对于k=1,…,n,计算仅从序列中取k个元素aI1,aI2,…,aIk,满足I1<I2<…<Ik,使得式子∑ki=1i⋅aIi最大,输出这个和。
提供一道题目。
记V(i)表示仅取i个元素的最优集合,官方题解已经证明了V(i+1)可以通过向V(i)加入一个新元素得到。因此我们只需要每一步加入贡献最大的元素。
考虑已经加入k个元素,现在考虑元素i的贡献,它的贡献为(p+1)ai+suf,其中p表示V(k)中下标小于i的元素数量,而suf表示下标大于i的元素和。
所以显然问题变成了有n条直线,第i条直线的公式为aixi+bi(这个ai与题目中的ai已经没有关系了,表示斜率),初始的时候xi=1。要求支持三种操作:
- 删除一条直线。
- 将一段区间中的直线的x属性增大1
- 将一段区间中的直线的b属性增大t
- 查询当前贡献最大的直线。
这个用线段树是解决不了的,需要使用分块。一般分块结合排序的时间复杂度是O(n√nlog2n)。但是由于属性a不会修改,因此我们实际上可以预先对a属性排序,相同b属性的我们取较大者即可。这样时间复杂度为O(n√n)。
图论
拓扑序
题目1:给定一个包含n个顶点的树,记Gv表示将顶点v作为根,将所有边的改变为从父亲指向孩子的有向边。记Topo(Gv)表示这个有向图的总拓扑序的数目。要求找到max1≤i≤nTopo(Gi)。
提供一道题目。
考虑存在边(u,v),Topo(Gu)和Topo(Gv)的区别。
记f(Gu,v)表示以u为根时,删除v为根的子树后的拓扑序,而s(Gu,v)表示以u为根,以v为根的子树的大小。可以发现Topo(G_u)=f(G_u,v)\times f(G_v,u)\times {n-1\choose s(G_u,v)}。同理有Topo(G_v)=f(G_v,u)\times f(G_u,v)\times {n-1\choose s(G_v,u)}。此时有\frac{Topo(G_u)}{Topo(G_v)}=\frac{s(G_v,u)}{s(G_u,v)}。
通过上面式子可以发现,如果顶点u下有一个子树大小超过\frac{n}{2},则这个顶点一定不是最优的。因此最优的点仅可能是重心,重心如果有两个,则任意一个重心都是最优的。我们只需要找到重心,之后算一下拓扑序就可以了。
最长公共子序列问题
题目1:给定长度为n的序列a和长度为m的序列b,要求找到它们的最长公共子序列的长度。其中1\leq n\leq 1000,1\leq m\leq 10^6。
最简单的LCS算法是定义dp(i,j),表示a长度为i的前缀和b长度为j的前缀的最长公共子序列的长度。但是这样做的时间复杂度是O(nm),太慢了。
由于LCS的长度不会超过\min(n,m),因此我们定义dp(i,j)为a的长度为i的前缀和b的某个前缀拥有长度为j的公共子序列,b的前缀的最小长度。这样我们的状态仅n^2个。但是这样转移需要快速解决这样一个问题:记f(i,x)表示b中最小的下标j,满足j>i,且b_j=x。这个问题可以通过持久化技术解决,这样我们的每次dp转移的时间复杂度为O(\log_2m),总的时间复杂度为O(n^2\log_2m)。
最小总和
树上最小总和
题目1:给定n个顶点的树,每个顶点i有一个权值w_i。现在在其中k个顶点a_1,a_2,\ldots,a_k上各有一个棋子,要求将这些棋子移动到b_1,b_2,\ldots,b_n上,其中在a_i的棋子需要移动到b_i,每次你只能选择一个棋子向某个邻近的顶点移动。称所有棋子所在的顶点的总和(一个顶点上可能有多个棋子,则统计多次)为潜能,要求移动过程中出现的潜能的最大值最小。问这个最小的潜能值是多少。这里1\leq k\leq n\leq 1000。
提供一道题目。
首先我们可以用一个向量来维护所有顶点所在的顶点信息。接下来我们考虑贪心算法。记初始向量为A,f(A,t)表示字典序最小的向量(两个顶点的排序规则是第一关键字为权值,第二关键字为编号),满足在潜能上限不超过t的前提下能从A通过若干步骤转移到f(A,t)。
考虑到不同的棋子的移动是独立的,因此我们可以同时让所有棋子移动到最小的顶点。考虑给定潜能上限t,之后我们贪心将所有顶点向具有更小权重的顶点移动(在不违背上限的前提下)。这里我们给出了计算f(A,t)的算法,时间复杂度为O(n^2\log_2n)。
很显然在给定潜能上限t的前提下,是否能从A转移到B等价于f(A,t)=f(B,t)。我们要找到最小的t,满足这个公式。这里可以使用二分,则时间复杂度为O(n^2(\log_2n)^2)。但是我们可以通过增量的方式,不断增大t,同时考虑造成的影响,这样就能去掉二分,将时间复杂度优化到O(n^2\log_2n)。
给定和最大乘积
题目1:给定总和m,要求选择一个n并找到一组正整数x_1,\ldots,x_n,满足\sum_{i=1}^n x_i=m,且\prod_{i=1}^nx_i最大化。其中1\leq m\leq 10^{18},输出(\prod_{i=1}^nx_i)\mod p,其中p为某个正数。
当m\leq 1的时候,问题是很简单的,这里不讨论。
首先我们可以发现对于t\geq 4,我们始终可以将其拆分为t-2和2,且二者的乘积不小于t。因此我们可以断言x_i仅可能为2或3。
同时可以发现当2出现达到3次的时候,由于2^3<3^2,因此我们可以将3个2替换为2个3。故我们只需要暴力枚举2出现的次数,仅可能为0,1,2次。
考虑r=m\pmod 3。
- 如果r=0,那么2只可能出现0次。
- 如果r=1,那么2只可能出现2次。
- 如果r=2,那么2只可能出现1次。
这样我们不需要比较操作就可以得到最优解。
置换与交换
置换与交换类型的问题非常常见,问题一般就是现在有一个置换,每次操作允许交换置换中的两个元素。问最少排序需要的步骤数。
这个东西题目出的非常多。
题目1:给定一个1,\ldots,n的置换p,每次操作允许交换两个元素,要求计算将其排序的最少步骤数。
创建一个包含n个顶点的有向图,并对于1\leq i\leq n,加入边(i,p_i)。图可以分成若干个环,记环的数目为C。
考虑一次操作,最多使得一个环分裂成为两个,或者将两个环合并为一个。由于当置换有序的时候,总共会存在n个环,因此答案为n-C。
时间复杂度为O(n)。
题目2:给定n个二维平面点,不存在任意三个点共线。第i个点上写着数字a_i,且a_1,\ldots,a_n是1,\ldots,n的一个排列。每次操作选择两个点,在它们之间画一条线段,之后交换两者上写的数字。要求执行一些操作后,使得对于1\leq i\leq n满足第i个点上写着i,且画的线段除了端点外不会相交。
提供一道题目题目。
首先我们不考虑那些初始就写着目标值的点。建环后可以发现所有环的长度都至少为2。
接下来我们将所有环合并。具体的做法就是我们选择一个最靠下的点z(有多个就选择最靠左的)。之后将其余点按照以z为中心的极角排序。
之后可以按序遍历其余点,如果点i和点i+1不在同一个环中,就交换这两个点。
可以发现如此操作后,所有点都在一个环上了,并且出现的线段都是在相邻极角之间。这意味着z到任意点的射线都不会与这些线段相交。
之后问题我们可以把z与z上所写数字对应的点交换,重复这个过程直到z和z上所写数字相同。
时间复杂度为O(n\log_2n)。
最小差值问题
题目1:给定两个长度为n的序列a_1,\ldots,a_n和b_1,\ldots,b_n,可以任意选择一些下标,如果你选择了i,则会将a_i提换为b_i。要求让a中最大值和最小值的差值尽可能小,找到最小的差值。其中1\leq n\leq 10^6,-10^9\leq a_i\leq 10^9。
首先我们可以不妨认为对于所有1\leq i\leq n,都有a_i\leq b_i(否则你先交换两个数即可)。之后我们暴力枚举允许出现的最小值,并维护当前的最大值。由于最小值只有2n种情况,因此只会发生2n次的最小值替换,我们从小到大考虑这些最小值。假设我们选择了最小值x,那么由于我们现在维护的一些值可能因为小于x因此变得非法,我们需要尝试替换为较大的值。我们可以用一个最小堆来维护这些候选人,同时由于最大值始终递增,只需要一个变量维护即可。总的时间复杂度为O(n\log_2n)。
题目2:给定长度为n的序列a_1,\ldots,a_{n}。要求将这些数分成任意组,每组至少有一个数,最多有两个数。要求组中值之和的最大值和最小值的差距尽可能小,输出这个最小值。其中1\leq n\leq 5000,-10^9\leq a_i\leq 10^9。
提供一道题目。
假设a是从小到大排序的。
假设最终被配对的数从小到大排序为b_1,\ldots,b_k。如果b_1与b_k不配对,而b_1与b_i,b_j与b_k配对,那么我们重新将b_1与b_k配对,而b_i与b_j配对,由于b_1+b_i\leq b_1+b_k,b_i+b_j\leq b_j+b_k。因此新的配对不会违背任何约束。继而我们可以推出b_i与b_{k+1-i}配对。
首先如果所有数都不是正数,那么我们将所有数全部取反,这样得出的结果是不变的。
当所有数都非负的时候。如果存在i<j,a_i未参加配对,但是a_j参加了,那么我们可以知道最小值不超过a_i。这时候我们将a_i与a_j位置交换,可以发现新的结果不会变坏。因此我们可以断言参与配对的元素一定正好形成a_1,\ldots,a_n的一个前缀。我们可以暴力枚举所有前缀并计算差值,这样就提供了一个O(n^2)的算法。
最后我们考虑a中同时有正数和负数的情况,即a_1<0且a_n>0。那么可以发现a_1和a_n必定都参与匹配,根据第一块内容可知a_1与a_n在同一组情况是最优的。弹出a_1与a_n合并为同一组,重复这个流程直到a中只剩下非负或非正数,记录L,R为已知组形成的范围。之后用之前提到的算法即可O(n^2)解决剩下的内容。
区间减法问题
题目1:给定长度为n的序列a_1,\ldots,a_n,要求执行下面操作若干次,使得序列中所有元素都变成0。
- 每次操作选择一段连续的区间,将区间中所有元素减少1。
要求找到达成目标的最小操作数,并输出。其中1\leq n\leq 10^6,且1\leq a_i\leq 10^9。
引入一个新变量a_0=0,如果存在i\geq 1,满足a_i>a_{i-1},那么很显然我们至少需要有a_i-a_{i-1}个操作,起始于下标i。
于是我们找到了一个结果的下界\sum_{i=1}^n\max(a_i-a_{i-1},0)。接下来我们还要证明这个下界同时是可以达到的。
事实上,如果a_i\leq a_{i-1},那么我们可以继承所有之前未终止的操作。而如果a_i>a_{i-1},那么我们可以继承之前的a_{i-1}个操作,并额外创建a_i-a_{i-1}个操作,以i作为起始。很显然这种方式得到的操作数为\sum_{i=1}^n\max(a_i-a_{i-1},0)。
因此答案为\sum_{i=1}^n\max(a_i-a_{i-1},0),时间复杂度为O(n)。
题目2:给定长度为n的序列a_1,\ldots,a_n,要求执行下面操作若干次,使得序列中所有元素都变成0。
- 每次操作选择一段长度在至少为L,且最多为R的连续的区间,将区间中所有元素减少1。
要求找到达成目标的最小操作数,并输出。其中1\leq n\leq 10^6,且1\leq a_i\leq 10^9,n\geq R\geq 2L-1,L\geq 1。如果无解则输出-1。
提供一道题目。
首先考虑当R=N的时候,如何解决这个问题。很显然我们可以贪心来做。具体就是我们维护一个集合S,S中存储所有未闭合的操作区间。之后从左到右扫描我们的序列a。考虑现在处理的是a_i,这时候有两种情况:
- 一种是|S|>a_i,这时候我们必须闭合S中的一些序列。我们可以随意挑一些长度至少为L的区间,关闭,并从S中移除。如果剩下的都是长度不超过L的区间,那么就无解。
- 另一种情况就是|S|<a_i,这时候我们必须新建一些操作区间,我们可以插入a_i-|S|个新的操作区间(注意开始下标相同的操作区间,我们可以合并并记录它的重复次数),以i作为起始下标。
在扫描完所有的元素后,我们要关闭集合S中所有的操作区间(如果有长度小于L的区间,则无解)。
上面的算法正确性和最优性是很显然的。
下面我们考虑R任意的情况。这时候我们实际上可以认为一个操作区间长度可以超过R,只不过需要额外的费用而已。事实上,如果一个区间长度为x\geq L,那么我们一定可以将它分解为\lceil x/R \rceil个操作,具体就是按R切分成若干段,很显然只有最后一段长度x \pmod R可能小于L,这时候由于R+(x\pmod P)\geq 2L,因此我们可以将最后一段和倒数第二段合并后重新分解为两端长度满足条件的操作序列。同时可以发现继续复用之前遗留的操作区间比新建操作区间一定更优。
因此我们可以按照上面的逻辑来实现我们的算法,只不过在从S中删除序列的时候,应该贪心删除b-i\pmod R最小的操作区间,其中b表示区间的开始下标。
我们可以用一个队列来维护长度小于L的操作区间,并用链表来维护长度达到L的操作区间,用VEB Tree可以O(\log\log R)找到某个点的前驱和后继。因此总的时间复杂度为O(n\log\log R)。
树上最小遍历距离
题目1:给定一颗大小为n的树,每条边都有对应的边权。现在要求找到一个遍历顺序v_1,\ldots,v_n,其中1,\ldots,n在其中各出现一次,并且\sum_{i=1}^{n-1}dist(v_i,v_{i+1})最小化。其中1\leq n\leq 10^6,且边权非负。
首先考虑一个如果必须从v_1出发后回到v_1,那么很显然最优遍历序至少需要遍历每条边两次,并且这个下界是可以达到的(考虑最简单的递归算法)。因此这时候的解为\sum_{e\in E}w(e)。
现在回到这个问题,我们不要求必须回到v_1,那么相当于从\sum_{e\in E}w(e)中减去dist(v_n,v_1),前者是常数,因此我们要让路径最短,等价于让dist(v_n,v_1)最大化。现在问题就成了找到树上最远的一对点,就是树上的直径,而这个问题可以O(n)解决。
题目2:给定一颗大小为n的树,每条边都有对应的边权。现在要求选择k个1到n之间的不同的数v_1,\ldots,v_k,并且\sum_{i=1}^{k-1}dist(v_i,v_{i+1})最小化。其中1\leq n\leq 3\times 10^3,且边权非负。
提供一道题目。
先阅读题目1的解法。
考虑包含所有k个选中顶点的最小连通块(仅保留必要的顶点,让选中顶点两两连通),如果这个最小连通块中包含非选中顶点,我们一定可以将某个选中顶点替换为其中的非选中顶点,且结果不会变差。
因此现在复述一下问题,我们要找到一个大小正好为k的连通块,且它的总边权之和减去直径长度最小。
我们可以建立dp(u,i,j)表示以连通块以u为根,包含正好i个顶点,包含直径的j个端点,最小的总边权之和减去包含的直径部分长度的值。
这个dp实际上类似树上卷积,每对顶点仅在lca处贡献一次。因此总的时间复杂度为O(9n^2)。
向量
最小切割向量
切割向量是指给定一组k维向量A,要求找到一个最小向量v,满足0\leq v_i,且对于所有a\in A,都至少存在一个下标1\leq i\leq k,使得a_i\leq v_i。在多个满足条件的v中,要求\sum_{i=1}^ka_i最小化。
考虑k=1的情况下的最优解。很显然我们要选择最大值。时间复杂度为O(n)。
考虑k=2的情况下的最优解。我们可以通过排序去掉一个维度。具体做法就是我们先按照第2个维度排序。很显然我们需要选择一个后缀,后缀部分通过第二个维度来解决,而其余的前缀部分通过第一个维度来解决。当我们从后向前扫描的时候,我们可以同时维护后缀的最大值,而前缀的最大值有前缀最后一个元素决定。时间复杂度为O(n\log_2n)。
考虑k=3的情况下的最优解。这时候也是可以做的。具体做法就是按照第三个维度排序,从而类似k=2的情况,我们需要考虑后缀。现在的问题变成,我们需要维护一个数据结构,其保存一个二维向量集合。它需要支持插入一个二维向量,并且回答这个向量集合的最小切割向量。
我们考虑如何维护这样的集合。我们可以把二维向量看成平面上的一个点,如果一个点右上角有其它点,这个点可以被删除而不会影响结果。现在剩下的点,按照第一维排序后,第二维呈现递减的性质。
我们可以发现切割向量一定是相邻的两个向量所决定。我们可以用一个多重集合维护所有的间隙以及这些切割相连的状态。而插入新的点,可以会导致一些旧点被删除,我们可以同时维护间隙的删除和插入。
于是总的时间复杂度为O(n\log_2n)。
提供一道题目。
对于k>3的情况,我也不知道有什么好办法。
最大线段分组交
给定n条线段,要求将它们最多分成k个组。每一组的价值为其中所有线段的交的长度,而总价值为所有组价值之和。要求找到在给定条件下的最大价值。
提供一道题目。
要解决这道问题需要以下观察。
- 最多一个组的价值为0
- 如果线段a包含线段b,则a要么和b同组,要么a单独构成一组。
因此我们可以分类讨论:如果有一个组的价值为0,那么这时候我们会选择长度最大的k-1个线段单独构组,这时候问题是非常简单的。
现在考虑所有组价值都不是0。我们把线段分成两类,如果一个线段包含至少一个子线段,那么这个线段为第一类,其余线段为第二类。
这时候问题变成了计算f(t),即将第二类线段分成t组的最大价值,而另外k-t组则分配给第一类线段。我们要计算f(0),f(1),\ldots,f(k)。可以发现第二类线段之间相互不包含,我们将第二类线段按照左端点进行排序。结合所有分组的价值均非0,可以直接得出结论,最优方案下同组线段一定是下标连续的若干个线段组成。
于是我们可以得出一个dp算法,令dp(i,j)表示将前j个线段分成i组的最大价值。这个dp可以O(nk)进行计算。
因此总的时间复杂度为O(n\log_2n+nk)。
矩阵最少上色
提供一个n\times m的矩阵,初始所有的单元格的值都是-1。之后可以执行多次操作,每次操作选择一行/列以及一种整数c,将选中的所有元素都改成c。
现在给定最终的01矩阵A,判断是否可能通过上述操作得到该矩阵,如果可以,则需要找到最少操作次数。其中1\leq n,m \leq 2000。
提供一道题目。
我们逆向操作,上色等价于删除同色的一行或一列,要求最终将整个矩阵全部消除。
很显然我们可以贪心删除,得到一个O(nm)的算法来判断是否有解。
由于要么行被全部删除,要么列被全部删除,不失一般性,我们可以认为列被全部删除。
现在考虑在列全部删除的情况下,没有被删除的行的数值显然是固定的,因此我们可以得出一个结论,最后只能剩下一些完全相同的行。我们要最小化总的操作步数,等价于要最大化保留的行数。并且在最优解的情况下,很显然相同的行是被同时删除或同时保留的。
现在我们将行按照行的值进行分组。对每个组验证是否存在一种方法,在不删除这组行的前提下删除所有的列。
如果我们使用上面提到的贪心算法,这样得到的时间复杂度为O(n^2m)的算法。
下面我们给出一个结论,在存在解的前提下,我们可以选择保留任意一组相同的行(或列),这时候同样有解。于是我们得到了一个O(nm)的算法。
这个结论证明非常直观。首先我们考虑在整个流程中,可以发现相同的行始终保持相同,而不同的行则始终不同,列同样。因此我们可以将相同行合并,同样将相同列合并。
考虑有解的情况下,我们记R(A)表示A中有多少行值全相同,C(A)表示A中有多少列值全相同。可以发现初始的时候有R(A),C(A)\leq 2。并且我们如果我们选择删除一行,则R(A)必定减少1,而C(A)最多增加1。对应的如果我们删除列,那么C(A)必定减少1,而R(A)最多增加1。
下面我们假设我们要保留某行r,那么我们始终优先删除其余行列。最好的情况是最后只剩下r。否则我们这时候应该有R(A)=1且C(A)=0。由于存在解,因此应该每次删除行后会新增一个可行列,删除一列后会新增一个可行行。实际上如果允许行列重新排列,会发现这样的矩阵形如:
00000
10001
11011
11001
10000
或
0000
1001
1101
1000
很显然这时候有R(1)=C(1)=1。因此不可能会出现R(A)+C(1)=1的情形。这也意味着我们一定可以保留第r行。
最大和交错数列
给定长度为n的整数数组a。定义S(k)表示a的所有长度为k的子序列。记f(S(k))=\max_{b\in S(k)} g(b),其中g(b)=\sum_{i=0}^{|b|-1} (-1)^i b_i。要求找出f(0),f(1),\ldots,f(n)。
原题。
这里我们可以用DP做到O(n^2)的时间复杂度,但是我们实际上可以做到O(n\log_2n)。
我们记B_i表示a的某个长度为i的子序列,满足g(B_i)=f(S(i))。可以证明B_i一定可以通过B_{i+2}删除两个元素后得到。证明放在后面。
我们可以将f(i)按照i的奇偶性分成两类,分别计算。如果我们已经知道B_i,要计算B_{i+2},它的计算很类似于DP,并且可以发现分治的时候需要维护的状态是常数大小的,因此我们可以在线段树上做DP,时间复杂度优化到O(n\log_2n)。
下面给出证明。对于B_{k+2}和B_{k},对于给定l<r,且a_l,a_r\in B_{k+2},从B_{k+2}中删除a_l和a_r后得到的新的序列$B'k,记c(l,r)=g(B{k})-g(B'_k),显然我们要最大化c(l,r),设c(l,r)$为最优解。
如果$g(B'k)<g(B_k),考虑存在一个数组d,初始都是0。如果a_i\in B{i+2},我们将d_x增加1,如果a_i\in B_{i},则将d_x减少1$。
首先我们找到最小的下标R,满足d_R=2,同时我们可以找到最大的下标L<R,满足d_L=0,可以发现d_{L+1}=1。可以发现对于i\in (L,R),a_i只有可能同时出现或不出现在B_{k}和B_{k+2}之中。我们可以发现从向B_{k}中加入a_L和a_R后得到$B'{k+2},有g(B'{k+2})=g(B_{k})-c(L,R)>g(B'k)-c(L,R)\geq g(B'_k)-c(l,r)=g(B{k+2}),而由于g(B_{k+2})$是最大结果,因此这是不可能的。
最小字典序矩阵
要求找到一个kn行m列的矩阵,每个单元格中包含0到k-1之间的值,且对于每一列,对于任意0\leq i<k,i都出现正好n次。且不存在相同行(所有行两两不同)。
判断是否有解,如果有解,则要求最小化最大字典序行。其中1\leq kn\leq 10^5,1\leq m\leq 20,1\leq k<10。
提供一道题目。
显然必要条件是k^m\geq kn,否则一定无解。
我们可以把每一行视作一个k进制整数。
很显然正好有n行的第一列值为k-1,而为了让最大字典序行最小化,同时每行都不同,我们希望能令这n行为(k-1)k^{m-1}+i,其中0\leq i<n。
下面我们考虑其他行。对于j开头的行,我们可以令这n行为j\times k^{m-1}+i,其中0\leq i<n。
可以发现上面我们可能无法保证每一列每个整数出现次数均为n。但是我们可以做一个简单的置换,将以j起始的行,每一个单元格(除了第一列)都应用置换(i+1 i+2 \ldots i)。
这时候可以发现每一列的所有整数都应该拥有相同的出现次数,即正好n次。
多重排列最大相同元素距离
对于1\leq i\leq n,有a_i个i。我们要将这S=\sum_{i=1}^na_i个元素进行排列,对于所有排列中我们要求最近的两个相同元素距离最大(下标的差的绝对值最大)。记M=\max_{i=1}^na_i,保证M\geq 2。
这个问题有非常简单的贪心解法。记共有k个不同元素出现次数为M。答案的一个上界为\left\lfloor \frac{S-kM}{M-1}+k \right\rfloor。
我们现在来聊聊具体的一个做法,非常简单我们把k个出现次数最多的元素(不妨设为1,2,\ldots,k),创建k个槽,每个槽维护一个链表,最终结果就是将这k个链表前后连接得到。首先每个槽中的元素均为1,2,\ldots,k。接下来我们分别考虑元素k+1,\ldots,n。对于第t个被考虑的元素,插入到第t\pmod{k-1}个槽的尾部。
这样我们就可以得到上界,自然也就是最优解。
最少逆序对
题目1:给出长度为n和m的两个序列a和b。现在希望将两个序列合并为一个长度为n+m的新序列,且要求序列a中元素的相对顺序不变。要求找到所有新序列中逆序对最少的。其中1\leq n,m\leq 10^6。
提供一道题目。
结论1:在最优解中序列b的子序列一定是有序的。
不妨设p_i表示b中最小的第i个元素在新序列中的出现位置。如果最优解中b无序,则存在某个下标i,满足p_i>p_{i+1}。这时候我们可以发现交换p_i和p_{i+1}后可以得到更少的逆序对。
记f_i表示b中最小的第i个元素x的最优插入位置(0到n),使得x与a的逆序对最少。
结论2:f_1\leq f_2\leq \cdots \leq f_m。
证明与上面类似,可以通过交换两个相邻的不满足条件的元素从而得到更优解。
因此我们可以发现这个问题中我们可以通过计算局部最优得到全局最优,通过计算f_i恢复出p_i。
而怎么计算f_i,我们可以用分治。类似整体二分,可以保证时间复杂度为O((n+m)\log_2(n+m)。