codeforces:818G Four Melodies分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了codeforces:818G Four Melodies分析相关的知识,希望对你有一定的参考价值。

题目

  题目大意是有一组自然数v1,...,vn,要求在其中找到四个非空子序列(从原来的自然数序列中挑选一部分数,并按原先后关系排序),这些子序列互不相交,且每个子序列中的前后元素的值要么差值的绝对值为1,要么对7取余的值相同。

  输入自然数序列的长度n满足4<=n<=3000,而每个输入的自然数均不超过1e5。

  求解所有满足以上条件的这样四个子序列的长度的总和的最大可能值。


 预处理

  这道题有点难度,考察的知识范围很大。我个人也是参考了codeforces上的tutorial。要解决这个问题必须掌握最短有向边和网络流等多方面的基础知识,这些内容不会在本文中被一一讲解,需要读者自己去查阅。

  先说明如何用已有的知识体系来解决。这个问题如果我们将每个序列中的自然数比作一个网络结点,而从这个结点向所有可以在同一子序列中作为后续数值的序列元素对应的结点蔓延出一条有向边,而这些有向边的费用均为-1,容量为1。这样求解4条子序列,就类似于求解从源点到汇点输送4单位流所花费最小费用,即最小费用网络流问题。当然这里没有限定每个结点只能被通过一次,可以将原来一个结点ak,拆分为ak.in和ak.out两个结点,其中原来所有以抵达ak的有向边都转由抵达ak.in(入点),而从ak出发的有向边则转由ak.out(出点)出发。而由一条从ak.in到ak.out的有向边(ak.in,ak.out)连接两个结点,且容量为1,费用为0。这样就能确保只有一单位的流能通过ak结点,对应与原问题中子序列互不相交的性质。这样我们就构建了一个网络N(A,E),其中A为2*n个顶点的集合,而E为创建的边,其数量不会超过|A|^2=O(n^2)。

  但是最小费用最大流问题中拥有多项式时间复杂度的算法都拥很高的时间复杂度,比如minimun-mean-cycle-canceling算法,其时间复杂度更是有O(|A|^2*|E|^3)的时间复杂度,当E的数目最大时,取值O(n^2),则时间复杂度可以达到O(|A|^8)。What the fuck!当取n=3000时,需要执行将近6561000000000000000000000000*k次运算,其中k是一个有上界的数值,妥妥的有生之年系列。

  为了避免上面这么尴尬的问题,我们需要继续深入理解条件。我们发现所有的有向边的出发结点F和抵达结点T对应的原序列中自然数FN和TN有如下性质,在原序列中FN一定出现在TN之前。我们可以利用下面的技术来压缩图中的边,去除其中的一些多余的边(可以被多条边拼凑而出):我们重新分割每一个结点ak,我们将ak切分为ak.in,ak.occupy,ak.out,其中ak.in和ak.out分别用于作为外部有向边的抵达结点和有向边的出发结点,且有向边(ak.in, ak.occupy)的容量为1,费用为-1,而(ak.occupy, ak.out)的容量为1,费用为0,(ak.in, ak.out)的容量为无限,费用为0。意即我们将费用转移到结点内部计算,而允许任意流免费通过(ak.in, ak.out)径直通过这个结点。这样我们就可以不必为每一个结点ai,以及可以作为后续结点(可以通过一条有向边抵达的结点)的结点aj,都创建一条有向边(ai,aj),我们只需要为每个结点ai,以及首个满足vi%7=vj%7(其中j>i,且是满足条件的所有j中的最小值)的结点aj创建一条有向边(ai,aj),同时为首个满足vi+1=vt的结点at创建一条有向边(ai,at),以及为首个满足vi-1=vl的结点al创建一条有向边(ai,al)。即每个结点都只能到达后续首个符合条件的至多三个结点的有向边,这样做依旧能保证新构建的图N(A,E‘)与原图N(A,E)具有相同的连通性,即在N(A,E)中从ai能找到一条路径抵达aj,而在N(A,E‘)同样也可以导出一条同等费用的路径从ai抵达aj,反之亦然。这样做看似使得图变得更加复杂了,但是却确实减少了边的数目,其中|A|=3n=O(n),而|E|=6n=O(n),即使为每个入点增加一条从源点出发的边,为每条出点增加一条抵达汇点的边,|E|=6n+2n=8n=O(n)。即我们成功地压缩了图,将边的数量约束在了O(n)。

  上面整个过程可以在O(n)的时间内完成,即在O(n)的时间内创建这样一个图。这个过程可以通过创建一个大的数组来实现订阅功能,或者通过散列算法来实现,这里不再赘述。

  尽管压缩了图,但是时间复杂度依旧维持在O(|A|^5),在|A|=3000的情况下,依旧是不现实的。


 算法实现

  首先讲述一个最小费用网络流中的一些定义:假设x为满足容量守恒的任意流,V(x):=从源点发出送入到汇点中的流量。

  接着讲述最小费用网络流中的基本定理:若x是图N(A,E)中的所有容量为V(x)的流中费用最小的流,而y是图N(A,E)关联于流x的残存网络N(A,E(x))中容量为V(y)的费用最小的流,那么x和y合并后的流z是图N(A,E)中所有容量为V(z)=V(x)+V(y)的流中费用最小的流。

  这个定理的证明非常简单,假设还存在一个比z费用更小的流f,满足V(f)=V(z)。记f‘=f-x。我们知道每一个网络中的流均可以被分解为至多|A|个从源点到汇点的路径和|E|个从循环圈的复合。而显然f‘中是不包含负费用循环圈,因为假如存在,那么在x中就存在对应的有残余容量的负费循环圈,那么我们就可以通过增广x中这些负费循环圈构建费用比x更低,但是V不变的新流x‘,这与x是图N(A,E)中所有容量为V(x)的流中费用最小的流这一前提相悖。而所有出现在f‘中的从源点到汇点的路径上的分支流f‘1,f‘2,...,f‘k的总流量与y一致,而由于y是所有N(A,E(x))中容量为V(y)的费用最小的流,因此y的费用一定低于f‘1,f‘2,...,f‘k的加总。而由于费用可以直接加总,因此cost(x+y)=cost(x)+cost(y)<=cost(x)+cost(f‘)<=cost(x+f‘),因此假设不成立,z确实是所有容量为V(z)的N(A,E)中的流中的费用最小的流。

  因此我们只需要连续四次在前面汇总流的残余网络中找到下一个容量V为一单位的最小费用流,并汇总到原来的流上。重复四次,就可以得到我们最终的结果。但是如何在一个费用网络中找到最小费用路径是一个困难的问题。

  这里可以采用加权的方案。我们为每个结点v分配一个加权值p(v),并且我们称b(u,v)=c(u,v)+p(u)-p(v)为边(u,v)的加权费用,其中c(u,v)表示边(u,v)的原始费用(按照预处理阶段的结果为-1或0)。注意对于任意路径(v0,v1,...,vk),其加权费用(即出现的各条边的加权费用的汇总)为b(v0,v1)+b(v1,v2)+...+b(vk-1,vk)=c(v0,v1)+c(v1,v2)+...+c(vk-1,vk)+p(v0)-p(vk)。即从u出发,终点为v的路径的加权费用为原始费用+p(u)-p(v),这也意味着所有从u出发,终点为v的路径在原始费用上的偏序关系在加权费用得到了保持,因此最小加权费用路径与原始费用下的最小费用路径在同一张图中是同一条路径。因此如果我们能使得每一条在N(A,E(x))中的边的加权费用都为非负,那么就可以利用最短路径算法(比如广度优先搜索,dijkstra算法)计算出从源点到汇点加权费用最低的路径,即原始费用最低的路径,这也就解决了在一个费用网络中找到最小费用路径这一难题。

  当然现在的难题是如何找到这样的一组加权值使得残余网络中每一条边的费用均非负。首先保证原始网络的加权值,为了使b(u,v)=c(u,v)+p(u)-p(v)>=0,即p(u)+c(u,v)>=p(v),这是不是很类似与最短路径算法中的路径关系,我们这里可以令源点的加权值为0,而其余点v则为

技术分享

由于原始网络中是不包含任何环的,因此这个方案可以保证能为每个结点u都分配到合适的权值,且任意以u为出发点的边的加权费用必定非负。之后我们执行一次最短路径算法找到从源到汇点的最小费用路径,之后沿着这条路径传入一单位的流,这样得到了我们流量为1的最小费用路径x1。对于i=1,2,3,由于每次沿着xi的增广操作都可能会导致N(A,E(xi))中xi上的反向边的出现(拥有与正向边相反非原始费用和加权费用),甚至可能导致原来的单向边变作双向边。这就意味着我们必须重新修改权值以保证残余边的加权费用非负。该如何进行修改呢,其实双向边的出现已经给予了我们提示,由于正向边的费用c和反向边的费用cr恒为相反数,即-c=cr,因此c>=0且cr>=0就意味着c=cr=0。因此我们只要保证所有xi上的边全部被赋予0加权值,而其余非xi上的边全部被赋予非0加权值就可以保证N(A,E(xi))所有的边都有非负费用。b‘(u,v)=b(u,v)+p‘(u)-p‘(v)>=0,即b(u,v)+p‘(u)>=p‘(v),这与最短路径算法中核心部分是一致的,即我们令p‘(u)表示从源点到点u,以p为加权函数得到的最短加权费用。这样也能保证所有在最短路径xi上的边(u,v)满足b(u,v)+p‘(u)=p‘(v),即b‘(u,v)=0。

  到此,所有难点都已经被克服了,下面说明一下整个流程的时间复杂度。利用动态规划,我们可以在O(|E|+|A|)时间内重新计算每个结点的权值,而同样在每次迭代中(共4次),利用广度优先搜索算法计算从源点到所有其它点的最短路径(最小加权费用),其时间复杂度为O(|E|+|A|),因此每次迭代的总费用为‘计算每个结点的权值’+‘计算单源最短路径’=O(|E|+|A|)=O(n)。而总共四次迭代,故总费用为4*O(n)=O(n)。加上预处理阶段的O(n)的时间复杂度,其最终时间复杂度为O(n)+O(n)=O(n)。


 代码实现  

  代码大概明天提供,肚子饿了!!! 

以上是关于codeforces:818G Four Melodies分析的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 3-外部配置

vi光标移动命令之二

LeetcodePower of Four

342. Power of Four

Spring_four

java 342. Four.java的力量