[Ynoi2013] 大学

非常好之 \(\texttt{lxl}\) 使我的代码旋转。

看到这个题,第一反应显然是如果我们能够每次确切的找到要除的数,然后用树状数组进行单点修改,那么就可以达到 \(\mathcal{O}( n\log V \log n)\) 的复杂度。

那么接下来就是考虑如何去找到能除的数。

首先,我们不难想到对于每个权值 \(v\),将他在数组中所有的倍数开一个 \(\texttt{vector}\) 存起来。容易发现,一个数因子个数大概在 \(\log ^2 n\) 级别,所以这样没有问题(当然应该用埃筛的思想进行储存,否则会被卡常)。

然后,我们发现,经过一次操作之后,我们需要将对应倍数的数进行删除。也就是说,当我们要除以 \(x\) 时,我们就会对对应的 \(x\) 中的元素进行遍历,如果当前遍历到的数因为前面的 \(x'\) 已经不是 \(x\) 的倍数了,那我们就把他从这个 \(\texttt{vector}\) 中删除,从而来保证复杂度。否则的话,就暴力树状数组修改。

接下来就是如何维护删除。由于我们每次是对 \(l,r\) 进行操作,所以我们需要先二分查找到第一个大于等于 \(l\) 的位置进行操作,故不能使用链表。

一个好的解决方法时平衡树,但是 \(\texttt{lxl}\) 他不同意,所以考虑传统艺能并查集,用并查集去维护删除的数(一个很常见的 \(\texttt{trick}\) 我竟然不知道)。

例如,对于每一个序列下标开一个 \(\texttt{father}\),例如有四个数时: \(1,2,3,4\),最初都完整的存在。

然后,如果我们要删除 \(3\),我们就考虑 \(fa_{findfa{3}}=findfa(3+1)\)\(fa\) 数组也就变成了 \(1,2,4,4\)

也就是说删除 \(i\) 的时候,我们就令 \(fa_{findfa(i)}=findfa(i+1)\)

枚举的时候,\(i\) 的下一个就是 \(findfa(i+1)\)

这样就可以规避掉删除的问题,不用去维护平衡树,并得到时间复杂度的保证,以及要将 \(x=1\) 的情况直接特判掉。

给一下核心代码吧:

for (int i = findroot(x, (lower_bound(p[x].begin(), p[x].end(), l) - p[x].begin())); i < p[x].size() && p[x][i] <= r; i = findroot(x, i + 1))
{
	int now = i;
	if(a[p[x][now]] % x == 0) add(p[x][now], a[p[x][now]] / x - a[p[x][now]]), a[p[x][now]] /= x;
	else fa[x][now] = findroot(x, now + 1);
}

\(\texttt{Submission}\)

posted @ 2024-03-14 17:30  Saltyfish6  阅读(2)  评论(0编辑  收藏  举报
Document