ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-08-19 19:31:50  阅读:304  来源: 互联网

标签:前缀 int SOSDP leq 2021 权值 operatorname 高维


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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有