sosdp(高维前缀和)

思想

sosdp跟状压差不多,以5(101)为例,状压记录的是(100, 1)这两个状态,而sosdp记录的是(101, 100, 1, 0)这四个状态,也可以说是他的子集。即sosdp[mask]存的是 所有的a[i],其中i&mask == i或者i|mask == mask。

实现方法

//dp[j]记录子集的和
for(int i = 0; i < n; i ++){
    for(int j = 0; j < (1 << n); j ++){
        if((j >> i) & 1) dp[j] += dp[j ^ (1 << i)];
    }
}

模拟过程

取二进制位数是3

 001010011100101110111
0a1a2a3a4a5a6a7
1a1+dp[0]=a1+a0a2a3+dp[2]=a3+a2a4a5+dp[4]=a5+a4a6a7+dp[6]=a7+a6
2a1+a0a2+dp[0]=a2+a0a3+a2+dp[1]=a3+a2+a1+a0a4a5+a4a6+dp[4]=a6+a4a7+a6+dp[5]=a7+a6+a5+a4
3a1+a0a2+a0a3+a2+a1+a0a4+dp[0]=a4+a0a5+a4+dp[1]=a5+a4+a1+a0a6+a4+dp[2]=a6+a4+a2+a0a7+a6+a5+a4+dp[3]=a7+a6+a5+a4+a3+a2+a1+a0

 

例题 

arc100_c

题意

给你一个n,然后是2^n个数下标从0开始,问你找到两个数下标是i, j,满足i | j <= k,使得这两个数的和最大。然后k分别取1到(2^n - 1),也就是说要输出2^n - 1 行最大值。

思路

i | j == k,说明i,j都是k的子集,那么要使得两个数和最大,只需要找到k的子集中最大值和次大值即可。然后i | j <= k,也就是说i | j == k - 1也可以,所以从左到右更新结果的最大值输出。

ac代码

#include<bits/stdc++.h>
using namespace std;
mt19937_64 rng(time(0));
#define io cin.tie(0);ios::sync_with_stdio(false);
#define ok(x, y) x >= 1 && x <= n && y >= 1 && y <= m
#define debug(x) cout<<#x<<"="<<x<<endl
#define lowbit(x) x&(-x)
#define pii pair<int,int>
#define mk make_pair
#define ll long long
#define ull unsigned long long
#define rs p<<1|1
#define ls p<<1
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 1e18;
inline ll read(){
    ll p=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){p=(p<<1)+(p<<3)+(c^48),c=getchar();}
    return f*p;
}
void print(__int128 x){
    if(x<0) {putchar('-'); x=-x;}
    if (x>9) print(x/10);
    putchar('0'+x%10);
}

int a[1 << 18];
pii b[1 << 18];
pii cal(pii a, pii b){ //就是四个数放在大顶堆里面,然后取出前两个数返回,就是所要的最大值和次大值
    priority_queue<int> q;
    q.push(a.first);
    q.push(a.second);
    q.push(b.first);
    q.push(b.second);
    int t1 = q.top(); q.pop();
    int t2 = q.top(); q.pop();
    return {t1, t2};
}
void solve(){
    int n; cin >> n;
    for(int i = 0; i < (1 << n); i ++) {
        cin >> a[i];
        b[i] = {a[i], -inf}; //存放最大值和次大值,当前只有一个数,所以次大值定为-inf
    }
    for(int i = 0; i < n; i ++){
        for(int j = 0; j < (1 << n); j ++){
            if((j >> i) & 1) b[j] = cal(b[j], b[j ^ (1 << i)]); //b[j]取子集中最大值和次大值
        }
    }
    int ans = -1;
    for(int i = 1; i < (1 << n); i ++){
        ans = max(ans, b[i].first + b[i].second); // 更新最大值,也就是1到i的最大值
        cout << ans << endl;
    }
}
int main(){
    // freopen("1.in", "r", stdin);
    // freopen("std.out", "w", stdout);
    // cout << fixed << setprecision(6)
    io;
    int t = 1; 
    // cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值