CodeChef - COVERING Covering Sets(sosdp)

链接

题面:

在这里插入图片描述
定义: R ( x ) = ∑ x & ( i ∣ j ∣ k ) = x A [ i ] ∗ B [ j ] ∗ C [ k ] R(x)=\sum_{x\&(i|j|k)=x}A[i]*B[j]*C[k] R(x)=x&(ijk)=xA[i]B[j]C[k]

题目就变成了 ∑ x ∈ [ 0 , 2 n ) R ( x ) \sum_{x\in[0,2^{n})}R(x) x[0,2n)R(x)


思路:

先用sosdp从前往后转移,求出A、B、C的状态,然后用一个dp数组存一下
d p [ m a s k ] = A [ m a s k ] ∗ B [ m a s k ] ∗ C [ m a s k ]   ( m o d   p ) dp[mask]=A[mask]*B[mask]*C[mask]\ (mod\ p) dp[mask]=A[mask]B[mask]C[mask] (mod p)
但是会发现不太对的地方,如求 d p [ 1111 b ] = A [ 1111 b ] ∗ B [ 1111 b ] ∗ C [ 1111 b ] dp[1111b]=A[1111b]*B[1111b]*C[1111b] dp[1111b]=A[1111b]B[1111b]C[1111b]
如果A中只有0001b,B中只有1000b,C中只有0100b
A [ 1111 b ] = ∑ x ∈ 1111 的 子 集 A [ x ] A[1111b]=\sum_{x\in1111的子集} A[x] A[1111b]=x1111A[x],所以A[1111b]不为0,B和C同理。算出来的dp[1111b]不为0,与事实不符了,所以要减去一些东西,这些东西就是当前mask的真子集。避免后效性,需要从后往前做sosdp来减去来自真子集的贡献。

一组 A、B、C的贡献是 2 ( A ∣ B ∣ C ) 中 1 的 数 量 2^{(A|B|C)中1的数量} 2(ABC)1,因为对于这些1,x在这些位置上可以选0或1。



参考代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
const ll mod=1e9+7;

ll pow_2[25];
ll A[1<<20],B[1<<20],C[1<<20],F[1<<20];

int count_of_one(int x){
    int ret=0;
    while (x){
        if(x&1){
            ret++;
        }
        x>>=1;
    }
    return ret;
}

int main(){
    pow_2[0]=1ll;
    for(int i=1;i<=20;i++){
        pow_2[i]=pow_2[i-1]*2%mod;
    }
    int n;
    scanf("%d",&n);
    for(int i=0;i<(1<<n);i++){
        scanf("%lld",&A[i]);
    }
    for(int i=0;i<(1<<n);i++){
        scanf("%lld",&B[i]);
    }
    for(int i=0;i<(1<<n);i++){
        scanf("%lld",&C[i]);
    }

    for(int i=0;i<=20;i++){
        for(int j=0;j<1<<20;j++){
            if(j>>i&1){
                A[j]=(A[j]+A[j-(1<<i)])%mod;
                B[j]=(B[j]+B[j-(1<<i)])%mod;
                C[j]=(C[j]+C[j-(1<<i)])%mod;
            }
        }
    }
    for(int i=0;i<1<<20;i++){
        F[i]=A[i]*B[i]%mod*C[i]%mod;
    }

    for(int i=20;i>=0;i--){
        for(int j=0;j<1<<20;j++){
            if(j>>i&1){
                F[j]=(F[j]-F[j-(1<<i)]+mod)%mod;
            }
        }
    }
    ll ans=0;
    for(int i=0;i<1<<20;i++){
        ans=(ans+F[i]*pow_2[count_of_one(i)])%mod;
    }
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值