关于子集问题的一种解法
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 做板子?
标签:multi,min,int,复杂度,dat,子集,关于,maxN,解法 From: https://www.cnblogs.com/LitDarkness/p/16595272.html