其他分享
首页 > 其他分享> > 2021 暑假训练学习 SOSDP与高维前缀和

2021 暑假训练学习 SOSDP与高维前缀和

作者:互联网

SOSDP 与 高维前缀和

高维前缀和

我们知道一维前缀和的写法:\(s_i = s_{i-1}+a_{i}\),那么对于二维前缀和呢?

显然,根据容斥原理,我们有

\[\operatorname{sum}(x_1,y_1)=s_{x_1,y_1}-s_{x_1,y_1-1}-s_{x_1,y_1-1}+s_{x_1-1,y_1-1} \]

处理二维前缀和还可以这么写,但是三维,四维呢?这玩意搞起容斥岂不是逆天?

实际上,我们还有一个处理前缀和的方式:

int a[N][N], s[N][N];
for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j)
		s[i][j] = a[i][j];
for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j)
		s[i][j] += s[i - 1][j];
for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= n; ++j)
		s[i][j] += s[i][j - 1];

这样处理是等价的(分维度处理前缀和,然后再合并),但是写起来更简单。

二进制与高维前缀和

我们来看一个例题

有 \(n\) 件物品,第 \(i\) 件物品的权值为 \(a_i\),多个物品组成一个集合,集合的权值为集合中所有物品权值的异或和。现在有 \(m\) 次询问,每次询问给定一个集合 \(S\),求出集合 \(S\) 的所有子集的权值和,以及其所有超集的权值和。

\(1\leq n\leq 20,1\leq m\leq 10^5,1\leq a_i \leq 10^6\)

这个题目看起来跟高维前缀和没任何关系,不是吗?

我们假设有三个物品,编号分别为1,2,3,价值分别为A,B,C。我们不妨令:

\[f_{0,0,0}=0,f_{1,0,0}=A,f_{0,1,0}=B,f_{0,0,1}=C,\\ f_{1,1,0}=A+B,f_{1,0,1}=A+C,f_{0,1,1}=B+C,f_{1,1,1}=A+B+C \]

我们好像发现,我们可以通过对应下标为0或者1来决定某件物品选还是不选,从而得到对应的某个状态的权值。我们可以每次询问暴力枚举,但是这个复杂度是 \(O(m2^n)\) 的。

那么,前缀和在这有啥含义呢?

我们做一次前缀和,有

\[s_{1,0,1}=f_{0,0,0}+f_{1,0,0}+f_{0,0,1}+f_{1,0,1} \]

某个状态的前缀和,其实就是该状态所有子集的权值之和!(不太看得明白的,可以二维模拟一下)

子集是前缀和,那么超集只要反着做一遍即可。

但显然,我们不可能开一个20维数组,能把手写麻。但是万幸的是,因为每一位只是0或者1,所以我们可以进行状态压缩,将其压进一个整数里面即可。若状态 \(x\) 是状态 \(y\) 的子集,那么有 \(x|y=y\)。

状态压缩之后,稍微习惯一下,然后就可以当作普通的高维前缀和来做了。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 25, V = 1 << N;
int n, m, a[N];
LL pre[V], suf[V];
int main()
{
    //read
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++i)
        scanf("%d", &a[i]);
    //build:建立f数组
    for (int j = 0; j < (1 << n); ++j) {
        int sum = 0;
        for (int i = 0; i < n; ++i)
            if (j & (1 << i)) sum ^= a[i];
        pre[j] = suf[j] = sum;
    }
    //pre & suf
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < (1 << n); ++j)
            if (j & (1 << i))
                pre[j] += pre[j ^ (1 << i)];
            else
                suf[j] += suf[j ^ (1 << i)];
    //query
    while (m--) {
        int k, v = 0;
        scanf("%d", &k);
        for (int i = 0, t; i < k; ++i) {
            scanf("%d", &t);
            v |= 1 << (t - 1);
        }
        printf("%lld %lld\n", pre[v], suf[v]);
    }
    return 0;
}

SOSDP

感谢heyuhhh的博客:高维前缀和总结(sosdp)

SOSDP解决的是这样一类问题:

对于所有 \(1\leq i < 2^n\),求解 \(\sum\limits_{j\subset i}a_j\) 。

直接暴力枚举子集的话,复杂度是 \(O(2^n*2^n)\),也就是 \(O(4^n)\) (严格讲,均摊后的复杂度是 \(O(3^n)\),但是反正不够优就完事了)。如果进行高维前缀和,就可以将复杂度压进 \(O(n2^n)\) 。

看起来有点玄学,其实就三行代码:

for (int i = 0; i < n; ++i)
    for (int j = 0; j < (1 << n); ++j)
        if (j & (1 << i)) f[j] += a[j ^ (1 << i)];

(其实和上面的高维前缀和就是一个东西

我们来看一个改造版的题目(题目来源:ARC100E):

给定 \(2^n\) 个数,分别记为 \(a_0,a_1,\cdots,a_{2^n-1}\)。

对于每个 \(1\leq k < 2^n\),求出 \(a_i+a_j\) 的最大值,且 \(i \operatorname{or}j\leq k\)

\(1\leq n \leq 18,1\leq a_i\leq 10^9\)

如果我们将条件改一下,变成 \(i \operatorname{or}j=k\),算出的答案记为 \(ans_k\),那么原题目的答案就变成了 \(ans\) 的前缀最大值了。但是,这个条件依然不太可做。

我们再改一下,变成 \(i \operatorname{or}j\subset k\),这样条件肯定比 \(i \operatorname{or}j=k\) 强,但是比 \(i \operatorname{or} j \leq k\) 要弱,所以我们可以根据这个来求,然后做一次前缀最大值即可。

我们需要找出状态 \(k\) 的两个值最大的子状态,如果暴力的话,这个复杂度显然有点高,所以我们可以魔改一下原来的SOSDP板子,将求和转化为求最大值和次大值即可。

#include<bits/stdc++.h>
using namespace std;
const int N = 1 << 20;
int n, a[N];
int Max1[N], Max2[N];
void update(int j, int k) {
    //更新最大值
    if (Max1[j] <= Max1[k])
        Max2[j] = Max1[j], Max1[j] = Max1[k];
    //更新次大值
    else if (Max2[j] <= Max1[k])
        Max2[j] = Max1[k];
}
int main()
{
    //read
    scanf("%d", &n);
    for (int i = 0; i < (1 << n); ++i)
        scanf("%d", &a[i]);
    //init
    for (int i = 0; i < (1 << n); ++i)
        Max1[i] = a[i], Max2[i] = -1e9;
    //pre
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < (1 << n); ++j)
            if (j & (1 << i)) update(j, j ^ (1 << i));
    //output
    int ans = 0;
    for (int i = 1; i < (1 << n); ++i) {
        ans = max(ans, Max1[i] + Max2[i]);
        printf("%d\n", ans);
    }
    return 0;
}

标签:前缀,int,SOSDP,leq,2021,权值,operatorname,高维
来源: https://www.cnblogs.com/cyhforlight/p/15163511.html