EGZ 定理的另一个确定性算法

在上篇文章中,不难发现瓶颈在问题规模为素数的情况,所以这篇文章只考虑素数情况,下边设这个问题规模为 \(p\),需要说明的是本文不区分集合和多重集。

以及本文仍然是复读,依照自己的理解增减更改了较多细节(其实写完发现也不算完全复读吧)。

规定记号:\(\displaystyle A+B=\{(a+b)\bmod p\,|\,a\in A,b\in B\}\)\(\{a_1,a_2,\dots,a_{2p-1}\}\) 为排序好的给定数列,\(d_i = a_{i+p}-a_i\),且 \(d_i\not=0\)


上篇文章中,其实问题已经转化为了:

对于集合 \(\{d_1,d_2,\dots,d_{p-1}\}\),找出其的一个子集 \(I\) 使得:

\[\sum_{d\in I}d\equiv -\sum_{i=1}^p a_p\pmod p \]

在上篇文章中,我们对于已有余数集合 \(A\),我们给出了对于 \(A+\{0,d_i\}\)\(O(\log p)\) 的做法,这样合并 \(p\) 次,代价就是 \(O(p\log p)\) 的,但是我们可不可以用相对小的代价合并一些子集和简单的大点的集合呢?

子集和最简单的集合类型大概是 \(\underbrace{\{a, a, \cdots, a\}}_{k \text{个} a}\),其子集和组成的集合是一个等差数列(Arithmetic Progression),为 \(\{0,a,2a,\cdots,ka\}\),下面这个集合会用到比较多,记 \(AP(a,k)=\{0,a,2a,\cdots,ka\}\),虽然有 \(k+1\) 个元素(在 \(k<p\) 的情况显然是没有重复元素的),但是这是由 \(k\) 个元素组成的子集和,所以下面称 \(AP(a,k)\) 的长度为 \(k\)


考虑对于已有的子集和 \(A\),如何去快速维护 \(A+AP(a,k)\),参考 \(k=1\) 也就是上篇中提到的关于 \(A+\{0,a\}\) 的二分算法,只需要找到 \(t_i\in T_{i-1}+\{0,a\}\),然后令 \(T_i=T_{i-1}\cup\{t_i\}\) 即可,类似的,是否可以快速找出一个 \(|T|=k\),使得 \(T\cap S = \varnothing\),且 \(T\cup S\subset S+AP(a,k)\) 呢?答案是肯定的,首先,类似之前的处理方式,把所有元素乘以 \(a^{-1}\),这样问题变为了 \(S+AP(1,k)\),不难发现,\(AP(1,k)\) 可以填充所有长度 \(\le k\)\(S\) 中的”空隙“,即如果 \(s\in S\),则 \(s+i\in S+AP(1,k)\),其中 \(i\le k\),考虑怎么去维护这个过程:

先找到一对 \(l,r\) 满足 \(l\in S\)\(r\not\in S\),一个朴素的想法是从 \(l\) 扫到 \(r\),每扫到一个 \(\not\in S\) 的就把它加入到 \(S\) 中,直到把 \(k\) 个元素加入到 \(S\) 中,至于优化,仍然是分治:

对于 \(l\in S\)\(r\not\in S\),设 \(\displaystyle mid=\left\lfloor\frac {l+r}2\right\rfloor\),我们先把 \([l,mid]\) 这部分填好,然后再去填 \([mid,r)\),这样,如果 \([l,r)\) 填完之后还没有总计 \(k\) 个元素加入到 \(S\) 中,就将 \(r\) 加入到 \(S\) 中。如果一开始就已经有 \(k\) 个元素加进去了,则退出这一过程(详见原文 Lemma 23 处的伪代码)。

对于函数每次返回且加入元素不够时都会将一个元素加入到 \(S\) 中,再加上递归深度是 \(O(\log p)\) 的,所以这部分的总复杂度为 \(O(\log p+k)\)


但是不难发现,\(AP\) 数量太多了,是 \(O(p)\) 级别的,如果直接合并的话是 \(O(p\log p)\) 的,不同的 \(AP\) 数量需要是 \(\displaystyle O\left(\frac n{\log n}\right)\) 级别的才能在合并的时候实现 \(O(n)\)

考虑如何减少不同的 \(AP\) 数量,我们发现,对于两组集合的和集满足:

\[\sum S'_i\subset \sum S_i \]

只要对于所有 \(s\in\sum S'_i\),找到一组 \(x'_i\in S'_i\) 使得 \(\sum x'_i=s\),存在一个反向的映射使得能构造出一组 \(x_i\in S_i\) 使得 \(\sum x_i=s\) 即可,也就是通过牺牲和集的复杂性来换取更少的总集合数量。

一开始我们要求的是:

\[\sum AP(d_i,1) \]

根据 Cauchy-Davenport 定理,只要保证对于转化后的等差数列 \(\sum AP(s_i,k_i)\) 的长度和 \(W=\sum k_i\ge p-1\) 即可,这样整个算法流程基本就是 \(\mathbb Z/p\mathbb Z\subset\mathbb Z/p\mathbb Z\) 了,能保证所有答案的存在性,接下来就是如何去设计这样一个压缩的操作保证长度和 \(W\) 不减的同时减少等差数列数量。


首先是一个显而易见的操作:

操作 0

\[AP(a,k_1)+AP(a,k_2)=AP(a,k_1+k_2) \]

这说明对于相同公差的等差数列可以随意合并和拆解。

然后是另一个:

操作 1

对于 \(ax=by\)\(x>y\) 来说,有如下操作:

\[AP(a,2x)= AP(a,x)+\{0,by\}\subset AP(a,x)+AP(b,y) \]

这一操作的恢复答案成本显然是 \(O(1)\) 的,而且对 \(W\) 的贡献是正的,对于这一操作来说,更重要的是如何快速进行尽可能多的操作。


下边记 \(AP[a]=k\) 表示 \(AP(a,k)\)\(vis_{ax}\) 表示上一个被 \(AP(a,x)\) 标记过的 \(a\)\(Len[a]\) 表示 \(a\) 目前标记的长度(即目前正在标记 \(a,2a,\cdots,Len[a]\cdot a\))。

为了满足操作 1 中 \(x>y\) 的条件,让 \(x\) 从小到大开始扫即可,对于一个 \(x\),遍历所有满足 \(AP[a]\ge x\)\(a\)

  • 如果 \(vis_{ax}=b\not=0\),设 \(y=ax/b\)\(\displaystyle Cnt=\left\lfloor \frac{AP[b]} y\right\rfloor\)(能拆出多少个 \(AP(b,y)\)

    • \(AP[b]\leftarrow AP[b]-y\cdot Cnt\)
    • \(vis_{kb}\leftarrow 0\),其中 \(AP[b]< k\le Len[b]\)(回滚标记)
    • \(Len[b]\leftarrow AP[b]\)
    • \(AP[a]\leftarrow AP[a]+x\cdot Cnt\)
  • \(vis_{ax}\leftarrow a\)\(Len[a]\leftarrow x\)

不难发现,假如 \(x\) 扫到 \(k\) 的时候,遍历完了所有满足条件的 \(a\) 后,对于所有满足 \(AP[a]\ge k\)\(a\) 都有 \(Len[a]=k\),而标记之间是没有冲突的,即 \(\sum Len<p\),因此所有满足这个条件的 \(AP\) 是最多是 \(\frac pk\) 个的,所以如果 \(x\)\(1\) 扫到 \(k\),这部分的时间复杂度就是 \(O(p\log k)\) 的,如果 AP 的数量 \(s\le \frac pk\),那么这一步的时间复杂度为 \(O(p)\)

另外,对于所有 \(AP[a]<k\)\(a\) 都有 \(Len[a]=AP[a]\),相当于这些等差数列都不重叠地待在了 \(\mathbb Z/p\mathbb Z\) 里,所以可以把 \(vis\) 数组当做合并阶段的初始集合 \(S\),这样就能保证不用合并小的 AP 而保证合并时的复杂度。

\(k=\log p\),这部分的复杂度就是 \(O(p\log\log p)\) 的。且处理完仅剩 \(\frac p{\log p}\)\(AP\),合并的复杂度就是 \(O(p)\) 的,所以总的时间复杂度为 \(O(p\log\log p)\)

作者的 \(O(p\log\log p)\) 算法实现详见 https://github.com/arvindf232/EGZ_algorithm/blob/main/Main.kt

我严重怀疑作者的算法最差是 \(O(p\log p)\) 的,因为观察代码实现里 packingLemma 主要部分第 328 行的终止条件,实际上是以 \(W\ge 2p\)\(t>k\) 同时作为终止条件(作者在 Theorem 26 中也说不能这么搞:"The important difference when using trim step only is that we cannot guarantee increase \(W\), so we cannot assume \(W ≥ 2n\) and drop the short APs as done"),而不是像文中描述的把被标记的元素直当成合并时候的初始集合来保证复杂度,但这么写感觉我也分析不出来复杂度,摆了(

(其实根据转换后的问题来说,也可以说在这个复杂度下解决了模素数意义下的子集和问题)


\(O(p\log\log\log p)\) 的算法就非常复杂了:

首先引入一个新操作:

操作 2

\(va=wb\),且 \(\gcd(v,w)=1\),令 \(x\ge v,y\ge w\),则:

\[z\cdot(vw-v-w+1)+AP(z,u)\subset AP(a,x)+AP(b,y) \]

其中 \(z=a/w=b/v\)\(u=xw+yv-2(vw-v-w+1)\)

首先和合并的时候一样令两边同时乘上 \(z^{-1}\),这样 \(a=w\)\(b=v\),只需要证明 \((vw-v-w+1)+AP(1,u)=[ab-a-b+1,(ax+by)-(ab-a-b+1)]\subset AP(w,x)+AP(v,y)=AP(a,x)+AP(b,y)\) 即可。

这个问题有点典,参见小凯的疑惑(Frobenius 硬币问题),最大的不可被表示的数为 \(ab-a-b\),所以从 \(ab-a-b+1\) 开始,所有数 \(g\) 都可以表示为:

\[g=ax'+by' \]

问题转化为了找出能满足 \(x'\le x\)\(y'\le y\) 的最大区间,首先假设 \(x'>x\),设 \(x''\) 为满足 \(x''\le x\)\(x''=x'-kb\) 的最大值,则 \(g=a(x''+kb)+by'=ax''+b(y'+ka)\),现在只需要验证 \(y'+ka\le y\) 即可,不难发现 \(x''\in[x-b+1,x]\),则如果 \(y'+ka>y\),即 \(g=ax''+b(y'+ka)\ge (x-b+1)a+(y+1)b=(ax+by)-(ab-a-b)\),所以 \(g\le (ax+by)-(ab-a-b+1)\) 的情况都可以保证存在一组 \(x'\le x\)\(y'\le y\) 满足 \(g=ax'+by'\)

分析下操作对 \(W\) 的贡献 \(u-x-y\)\(u\)\(x=v\)\(y=w\) 时取到最小值 \(2(v+w-1)\),故每次操作 \(W\) 至少增加 \(v+w-2\),此时 \(x=v\)\(y=w\)

对于这一操作恢复解可以通过 exgcd 实现,至于常数的偏移可以放在全局维护。

(这一段可以出一道好题了)


操作 2 的处理和操作 1 是基本一致的,只不过这里扫的是操作 2 中的 \(v\),和操作 \(1\) 中的 \(a\) 一样需要保证 \(v\le AP[a]\),注意到在第一步中 \(vis_{va}=w\not=0\) 的时候,可以保证 \(\gcd(v,w)=1\),假设 \(\gcd(v,w)=g>1\),则有 \(\displaystyle va=wb\Rightarrow \frac vga=\frac wgb\),因为 \(v\) 是从小到大开始扫的,所以不存在这种情况。

步骤如下:

  • 如果 \(vis_{va}=w\not=0\),设 \(b=va/w\)\(z=a/w=b/v\)

    • \(vis_{ka}\leftarrow 0\),其中 \(0\le k\le AP[a]\)
    • \(vis_{kb}\leftarrow 0\),其中 \(0\le k\le AP[b]\)(回滚标记)
    • \(AP[z]\leftarrow AP[z]+(AP[a]\cdot w+AP[b]\cdot v)-2(vw-v-w+1)\)
    • 整体偏移 \(bias\leftarrow bias+z\cdot(vw-v-w+1)\)
    • \(AP[a]\leftarrow 0\)\(AP[b]\leftarrow 0\)
  • \(vis_{va}\leftarrow v\)

这一操作可以让长度的总和 \(W=\sum k_i\)\(O(W')\) 的时间达到目标值 \(W'\)

下边给出证明:

设标记 \(vis\) 非零的数量为 \(E\),如果扫到 \(v\) 没有冲突,那么 \(E\leftarrow E+1\),标记花费 \(O(1)\) 的时间,主要关注 \(va=wb\) 也就是冲突的时候,时间复杂度瓶颈在回滚标记上即 \(O(AP[a]+AP[b])\),分析 \(W\) 在冲突后的变化同上边的分析最小为 \(v+w-2=AP[a]+AP[b]-2=O(AP[a]+AP[b])\),所以 \(W\) 每增加 \(O(1)\) 都需要 \(O(1)\) 的标记和回滚标记,得证。

至于每次恢复解只需要 \(O(\log(\min\{AP[a],AP[b]\})\),所需时间可以忽略。


接下来是下一步要用到的一些性质:

对于操作 2 来说,发现如果 \(v\)\(w\) 相对 \(x\)\(y\) 更小的话 \(W\) 会有有更大的增长:

假设 \(k_1v\le x\)\(k_2w\le y\),则 \(u-x-y\ge(k_1+k_2-2)vw+2v+2w-2-x-y\ge(k_1+k_2-2)vw+(2-k_1)v+(2-k_2)w-2\),当取 \(k_1=k_2=2\) 时,\(\Delta W=u-x-y=2vw-2=O(vw)\),说明在 \(\displaystyle v\le \frac x2\)\(\displaystyle w\le \frac y2\) 时, \(W\) 可以获得二次方级别的增长。

同时引入操作 2 的非互素版本:

操作 2

\(ax=by\),且 \(g=\gcd(x,y)\)

\[z\cdot(xy-xg-yg+g^2)+AP\left(z,\frac {2xy}{g}\right)\subset AP(a,2x)+AP(b,2y) \]

其中 \(z=a/y=b/x\)

在此不作具体证明,用 \(x\)\(y\) 取代非互素版本的 \(v\)\(w\) 并除以 \(\gcd\) 即可。


先通过操作 1 在 \(O(p\log k)\) 的时间让长度 \(\ge k\) 的 AP 数降到 \(\displaystyle\frac pk\) 个,

然后通过操作 2 在 \(O(p)\) 的时间让 AP 总长度 \(W\) 达到 \(2p\),由于在操作 1 中对于所有 \(AP[a]<k\)\(a\) 都有 \(Len[a]=AP[a]\),且 \(\sum Len[i]<p\),故可以把 \(<k\) 的 AP 全部删除,仍能保证 \(\ge k\) 的 AP 总长度 \(\ge p\)

现在我们得到了 \(\displaystyle O\left(\frac pk\right)\) 个 AP 且它们的长度和至少为 \(p\),接下来的算法可以在 \(O(p)\) 时间内找出 \(\displaystyle O\left(\frac p{k'}\right)\) 个 AP 且它们的长度和至少为 \(p\) 且和集为前者的子集,其中 \(k'\ge k^{\alpha'}\)\(\alpha'=3-\alpha\)


先进行预处理:

运行操作 2 直到 \(W\ge 7p\),然后运行操作 1 直到第 \(k\) 轮来排除 \(\le k\) 的短冲突(即如果操作 1 运行到 \(k\) 轮,则不存在 \(ax=by\),其中 \(a,b\le k\)\(AP[a],AP[b]\ge k\)),因为这里只有 \(\displaystyle\frac pk\) 个 AP,所以时间复杂度为 \(O(p)\)

首先处理特殊情况:如果所有 \(\ge k^\alpha\) 的 AP 和大于 \(p\),那么直接返回。

否则令 \(AP[i]\leftarrow \min\{AP[i],k^\alpha\}\),这里最多舍弃 \(p\) 个元素,所以在这步后 \(W\ge6p\)

然后是主要部分:

\(t\)\(k\) 开始扫,对所有 \(\displaystyle\frac {AP[a]}2\ge t\)\(a\) 进行如下操作:

  • 如果 \(vis_{ax}=b\not=0\),设 \(y=ax/b\)\(g=\gcd(a,b)\)

    • \(vis_{ka}\leftarrow 0\),其中 \(0\le k\le AP[a]\)
    • \(vis_{kb}\leftarrow 0\),其中 \(0\le k\le AP[b]\)(回滚标记)
    • \(\displaystyle AP'[z]\leftarrow AP'[z]+\frac{2xy}{g}\)(这里的 \(AP'\) 是不同于 \(AP\) 的目标数组)
    • 偏移 \(bias\leftarrow bias+z\cdot(xy-xg-yg+g^2)\)
  • \(vis_{ax}\leftarrow a\)

如果 \(\sum AP'\ge p\),退出该过程。

根据前边的预处理,我们有 \(k\le a,b\le k^{\alpha}\),如果 \(g\ge k^{\alpha-1}\),那么存在 \(1\le a',b'\le k\) 满足 \(a'x=b'y\),矛盾,所以 \(g< k^{\alpha-1}\)。再来看这次操作对 \(AP'\) 总长度的贡献,为 \(\displaystyle\frac{2xy}g\ge\frac{k\cdot k}{k^{\alpha-1}}=k^{3-\alpha}\),至于回滚标记的复杂度为 \(O(k^\alpha)\) 的,如果让目标集合 \(AP'\) 的总长度 \(\ge p\),则需要 \(\displaystyle O\left(\frac{p}{k^{3-\alpha}}\right)\) 次冲突,因为标记之间是无法产生冲突的,所以均摊下来时间复杂度在回滚标记上,最坏需要回滚 \(\displaystyle O\left(\frac{p}{k^{3-\alpha}}\cdot k^{\alpha}\right)=O\left(p\cdot k^{2\alpha-3}\right)\) 次,所以只要保证 \(2\alpha-3\le 0\),这整个流程就是 \(O(p)\) 的,原文取 \(\alpha=\frac 43\)

然后是一个细节:为了让 \(\displaystyle O\left(\frac{p}{k^{3-\alpha}}\right)=O(p)\) 次冲突发生,需要至少有 \(2p\) 个标记,考虑 \(<k\)\(AP\) 总和因为不存在冲突所以 \(<p\)\(\sum AP[i]/2\ge 3p\),故至少有 \(2p\) 个可以标记的。

因此,以起始的 \(k\) 开始,每次迭代 \(k\rightarrow k^{\alpha'}\),直到 \(k\ge \log p\),就得到了 \(O(p\log\log\log p)\) 的算法,之后 \(O(p)\) 时间合并即可。

不难发现,这个算法一定会附带巨大常数以及石山实现,所以作者说大概跑不过 \(\log\log\) 版的我觉得挺对。

这里没有讲的是原文 3.3.5 关于操作 1/2 过程中的 corner case,如果有想实现的可以啃下原文。

这大概是我看过最复杂的算法了,依照自己的理解尽可能优化改进精简了作者的思路,如有笔误或事实性错误请评论区留言。


Reference.

  • Y. H. A. Leung, Finding a solution to the Erdős-Ginzburg-Ziv theorem in O(n log log log n) time, arXiv:2507.08139, 2025.

  • S. Choi, H. Kang, D. Lim. Simple deterministic O(n log n) algorithm finding a solution of Erdos-Ginzburg-Ziv theorem. arXiv:2208.07728, 2022.





彩蛋

image


b99e24b8a5a899d13f1283bfee3b84fa_720

图文相关(

posted @ 2026-04-25 16:30  Rolling_star  阅读(43)  评论(0)    收藏  举报