UOJ Logo zhoukangyang的博客

博客

UOJ Round #27 题解

2024-05-13 08:15:16 By zhoukangyang

景点观光

idea, data, solution, std from Ecrade_

引理 1:不移动到子树内没有黑点的结点一定不劣。

我们的目标是遍历到所有黑点,而移动到子树内没有黑点的结点只会徒增操作次数。

引理 2:当移动到结点 u 的子树内时,遍历完 u 子树内的所有黑点再移动到 u 的子树外一定不劣。

若未遍历完 u 子树内的所有黑点就移动到了 u 的子树外,则后面仍需再次移动到 u 子树内遍历剩下的黑点,而这显然只会徒增操作次数。

引理 3:去除子树内没有黑点的结点后得到一棵新树,那么沿着新树的某种欧拉序移动一定不劣。

不妨令红点表示当前未遍历完子树内所有黑点的结点,绿点反之。

根据引理 1,去除子树内没有黑点的结点不会造成任何影响。

根据引理 2,若当前棋子在红点处,则棋子只能往下移动(即移动到深度更大的结点处)。

若当前棋子在绿点 u 处,则考虑离 u 最近且为红点的祖先结点 r

根据引理 2,棋子接下来只能移动到 r 子树内的某个黑点。

观察到在移动的过程中,棋子必然会经过 rr 的某个子结点。

那么在从 u 移动到 rr 的某个子结点的过程中,由于路径上除了 r 为红点,其余均为绿点,故一直往上移动是最优的。

接着,在到达 rr 的某个子结点后,棋子必然又在红点处,这时只能往下移动了。

移动到最后一个黑点后,显然我们一直往上移动至根节点即可。

所以,在整个过程中,我们发现棋子是不会走“回头路”的,其实这也是沿着欧拉序移动的充要条件。

引理 4:沿着新树的欧拉序移动一定是所有移动路径中总路程最短的。

显然,我们必须要经过所有新树的叶结点。

所以,每条边都需要被经过至少两次,而沿着欧拉序移动恰能达到这个理论下界。


注意到总路程 l 为定值(新树边数的两倍),若第二种操作进行了 x 次,则总操作次数为 x+(l2x)=lx

所以,我们仅需知道 x 在无限制的情况下的最大值 xmax 即可,这样对于每个询问,答案即为 lmin(k,xmax)

考虑使用动态规划计算出 x 取到最大值时的操作次数。

定义 fu,0/1,0/1 表示进入结点 u 的子树内时,棋子处在 u/u 的某个子结点处;遍历完 u 子树内所有黑点后,将要从 u/u 的某个子结点移动到 u 子树外的操作次数。

由于 fu,0,1fu,1,0 的情况是对称的,故我们仅考虑 fu,1,0

对于 u 的所有子结点,我们需找到一个合适的遍历顺序,使得依次拼接后操作次数最少。

容易发现,相邻两个子结点分别是 0 出和 0 进时,则中间只需进行一次操作,而其余情况则需进行两次操作,故一个直观的想法是 0 越多越好。

考虑贪心。

一般情况下,当 fv,0,0fv,0,1 时,取 fv,0,0 不劣。

一般情况下,当 fv,0,0>fv,0,1 时,取 fv,0,1 不劣。

fv,0,1fv,1,1 同理。

所以我们得出结论:一般情况下,对于 fv,0,0,fv,0,1,fv,1,0,哪个最小就取哪个,若有多个最小值,则哪个含 0 越多就取哪个。

至于这里的结论为什么只适用于一般情况下,后续会提及。

子结点与子结点之间的拼接情况已处理完,最后仅剩下首尾两处根结点与子结点之间的情况了。

显然,当根结点 u 和子结点 v 分别满足 1 进和 0 进或是 1 出和 0 出,即进/出 u 子树时处在的结点恰为 v 时,便不需要进行操作,而其余情况则需进行一次操作。

仍考虑贪心。

  • u 满足 00 出时:

首尾两处与子结点均需进行一次操作,而中间相邻子结点之间分别满足 0 出和 0 进的数量越多越好。

当没有 01 出或 10 出的子结点时,只能将所有 00 出的子结点紧挨着排,后跟着所有 11 出的子结点;

否则将一个 10 出的子结点排在首位,后跟着所有 00 出的子结点,再跟着 01 出、 10 出的交替排列,最后跟着所有 11 出的子结点。

  • u 满足 10 出时:

将所有 00 出的子结点紧挨着排,后跟着 01 出、 10 出的交替排列,最后跟着所有 11 出的子结点。

  • u 满足 11 出时:

u 仅有一个子结点,暴力考虑所有情况即可;

否则将所有 00 进的子结点紧挨着排,后跟着 01 进、 10 进的交替排列,再后跟着所有 11 进的子结点,最后跟一个 10 进的的子结点。

注意这里需要再考虑强制将所有子结点变为 00 出的子结点的情况,因为这时的贪心可能就不奏效了。注意此时若 u 为黑点则需中途再进行一次操作去遍历 u

至此,我们便用 O(n+q) 时间复杂度解决了本题。

509 号迷宫

idea, data, solution, std from JohnVictor

算法一(暴力)

直接 DP,复杂度 O(n2),可以通过第一个子任务。

算法二(部分分)

输出 0 可以通过第二个子任务。第三个子任务的答案可以用一些组合数简单表示。对于第四个子任务,可以看做 O(np) 个点加一个下三角,下三角部分的贡献可以简单反射容斥计算,剩下部分暴力即可。第五个子任务留给小常数 O(nnlogn) 的直接分块 NTT 和大常数正解。

算法三(正解)

下面的分析中,令块长 B=p

考虑平移指数基。具体地,对于每一列,考虑维护一个形如 P(x)/(1x)k 的多项式表示一整列的 DP 值构成的生成函数。

我们要支持的操作是加一个单项式,查询一项系数(这两个操作足以解决每一列有一个不能走的点的问题),将 k 加上 1(也就是一次前缀和)。

考虑定期重构。我们维护 P(x)(1x)k 的形式。每一次将 k 减小 1,变成了 0 就重构。这样,对于这个形式查询一项系数是 O(B) 的。

利用 1xp=(1x)p,重构的时候只用除以 (1xB),这个相当于隔 B 个做一次前缀和,复杂度 O(n)

主要的问题是加一个单项式之后再除以 (1x) 怎么办。这个只用额外维护成 P(x)(1x)k+Q(x)/(1x)k 即可。注意这里的系数还是可以 O(k) 来计算的,因为 Q 不超过 k 个非零系数,单个贡献可以 O(1)

所以现在的复杂度为 O(np+n2p),可以通过所有子任务。

算法四

考虑每 p 条斜线(即 i+j=t×p 的位置 (i,j))算一次 dp 值。转移的时候可以考虑对障碍点容斥,钦定一些障碍点被经过了,并对可能产生转移的两个障碍点之间计算贡献。

斜线 -> 斜线的贡献可以 O(n2p) 处理;障碍点 -> 障碍点,斜线 -> 障碍点和 障碍点 -> 斜线的贡献均可以 O(np) 处理。

时间复杂度 O(np+n2p)

红场阅兵

idea, data, solution, std from zhoukangyang. sol-114514,-1919810 from bulijiojiodibulido

APIO2024 会讲的。大家可以提前复习一下积性函数相关知识。UPD:apio 讲课的核心内容放在了 这里

算法 -114514,-1919810 是 bulijiojiodibulido 老师验题时用的做法,算法一、算法二是官方题解做法。

算法 -114514

t=gcd(i,j),如果我们提取 ij 中与 t 相关的所有因子,即令 i=tisi,j=tjsj,其中 gcd(ti,s)=gcd(tj,s)=1。那么有 f(ij)=f(si)f(sj)f(titj)

所以可以考虑枚举 ti,tj,满足它们有相同的素因子分布(即 rad(t)=rad(ti)=rad(tj),其中 rad(n)=p|np),然后计算上式。这个时候我们还有 gcd(si,t)=gcd(sj,t)=1 的条件,如果直接反演,那么复杂度比较高。

类似构造系数 g(yi,yj),满足 xi|yi,xj|yj,rad(xi)=rad(xj)g(xi,xj)=f(xixj)。令 F(n)=i=1nf(i)。那么答案为 ti,tj,rad(ti)=rad(tj)g(ti,tj)F(n/ti)F(n/tj)。因为我们能保证每对 ti,tj,前面加的总系数为 f(titj)

对于函数 g,在上面等式中,右侧是积性,也就是关于每个素因子是独立的,所以 g 的取值也是对于每个素因子都是独立的。所以只需要对于所有素数,预处理出 g(pi,pj) 的值(事实上是一个次数为 2(i+j) 的多项式),那么对于任意两个数,可以快速算出上述的 g 值。

这个时候直接枚举 ti,tj,也就是从小到大枚举每个素数,以及在 ti,tj 中的指数就能快速计算答案。枚举代价为 1n 内所有 rad 相同的数字的对数,好像是 O(n) 级别的,可以通过 3×107 的数据。

算法 -1919810

考虑如何加速这个过程,我们希望能快速计算出比较大的素数的贡献。

dpn1,n2,p 表示从大到小枚举因子到 pn1=n/ti,n2=n/tjg 的乘积。对于大于 n 的素数,它一定是从 (n,n) 转移向 (n/p,n/p),并且乘上的是一个四次多项式。

那么我们只需要预处理 n/p 相同的每段的素数的四次多项式的值,就可以快速完成这些素数的转移。对于剩下的素数,我们枚举它们的指数,从大到小转移即可。

如果使用记忆化,当 n=109 的时候,状态数大概在 2×106 级别,转移大概在 2×108 级别,使用精细的实现卡常数就可以通过。

实际大部分的状态都是在小素数的地方,所以可能可以从小素数枚举然后 meet in the middle,但是没编出来。

算法一

考虑把积性函数推广至二维,如果函数 f(x,y) 是二维积性函数,假设 pi 是从小到大第 i 个质数,x=ipiaiy=ipibi,那么 f(x,y)=if(piai,pibi)。容易发现我们要求的是二维积性函数前缀和。

考虑设计积性函数 g(x,y) 满足 g(pa,pb)=f(pa,1)f(1,pb)。对于这个函数,其前缀和 in1jn2f(i,1)f(1,j) 是相当容易计算的。观察 h=f/g。我们发现 h(pk,1)=h(1,pk)=0

暴力枚举 h 有值的位置,可以证明复杂度是 O(n)。期望得分 70 分。

算法二

考虑优化之前的算法,设计二维积性函数 g3 满足 g3 只在 g3(x,x) 处有值且值为 h(x,x),接下来令 h=h/g3

此时有值的 h(pa,pb)pP,a,b0a+b0)就满足 a,b1ab 了。

接下来暴力枚举 h 中的有值元素 (x,y),计算 un/x,vn/y(gg3)(u,v),把这些答案带上系数加起来就能得到最终答案了。

i=1Aj=1B(gg3)(u,v)=k=1min(A,B)g3(k,k)i=1A/kj=1B/kg(i,j)。暴力整出分块计算答案。

我们当然要筛 f(x,1)g3(x,x) 的块筛前缀和。可以证明,除此之外的复杂度不超过 O(n2/3logn)

其实还可以更快一点,不过没必要了。

评论

算法一的复杂度是不是也是 1 到 n 所有 rad 相同的数字对数啊,为啥上面复杂度还是好像是 O(n),下面就变成可以证明了
评论回复
zhoukangyang:因为两个题解是两个不同的人写的。
t3 算法一/二的复杂度是怎么证明的?不太会,可以具体说说吗
评论回复
zhoukangyang:我 apio 讲课讲了。现在可以在 https://www.cnblogs.com/zkyJuruo/p/18204106 看!

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。