关于子集问题的一种解法

关于子集问题的一种解法

1.引入

给定 \(n\) 以及两个长度为 \(2^n\) 的序列 \(a\)\(b\) ,对于每个 \(k\)
\(min_{i|j=k}{ \{a_i + b_j \} }\)

对于这类不可逆的运算,常规的 \(FWT\) 无法解决,当然也可能是我太菜

而暴力枚举的复杂度为 \(2^{2n}\) 无法接受,

这时候我们就需要用到分治乘法。

2.记号

以下记号借用了集合幂级数的一些内容。

考虑将长度为 \(2^n\) 序列 \(a\) 中下标二进制最高位为 \(1\) 的部分记录为 \(a^+\) ,为 \(0\) 的部分记为 \(a^-\) ,记录答案序列为 \(f\)

首先,显然 \(f = f^+ + f^-\)

并且,我们有

\(f^+ = min\{min(a^+ + b^-) , min(a^- + b^+ ) , min(a^+ + b^+)\}\)

\(f^- = a^- + b^-\)

注意到这时我们已经把问题分成四个长度为 \(2^{n-1}\) 的子问题,但是如果暴力分治求解复杂度还是 \(2^{2n}\) 并且还多了一堆常数

不过第一个式子可以做如下化简,

\(f^+ = min(min(a^+, a^-) + b^+, min(a^-+b^+))\)

\(min(a^+, a^-)\) 是可以在分治过程中处理的,

这是我们的分治过程为 \(T(2^n) = 3 T(2^{n-1}) + O(2^n)\)

由主定理知,该算法的复杂度为 \(\Theta(3^n)\)

如果采用合适的实现,空间复杂度可以做到 \(O(2^n)\)

3.程序实现

该方法实现实际上起来异常的简单。

int a[maxN], b[maxN], c[maxN];
int dat[maxN];
void multi(int *a, int *b, int *c, int n, int *dat) {
  // a,b为上文所定义,c为答案数组,dat为临时数组,用来保存a的内容,n为长度
  // 由于min运算的不可逆性质,必须开个数组记录原内容
	n >>= 1;
	if (!n) { c[0] = min(a[0] + b[0], c[0]); return; }
	multi(a, b, c, n, dat); 
	multi(a + n, b, c + n, n, dat);
	for (int i = 0; i < n; ++i) {
		dat[i] = a[i];
	  a[i] = min(a[i], a[i + n]);
	}
	multi(a, b + n, c + n, n, dat + n);
	for (int i = 0; i < n; ++i)
		a[i] = dat[i];
}

4.习题

FWT能做的它都能做。

貌似还没什么习题。

也许可以用 AT4168 做板子?

posted @ 2022-08-17 15:03  LitDarkness  阅读(50)  评论(0)    收藏  举报