优秀子序列c++代码

简洁题意
要求从 A 选出子集 B 使得 B 中没有两个数按位与等于 0。每个子集 B 的价值是集合内所有数的和加一的 φ 值。求所有子集 B 的价值之和。

分析
可以思考,因为要知道所有的集合 B 不容易,所以考虑计算价值相同的 B 的个数。

设 f[i] 表示所有值为 i 的子集 B 的数量,我们算出 f[i] 后乘上 φ[i+1] 就是这个方案的价值和,把每个价值和加在一起就是答案。

考虑怎么计算这个 f ,我们可以把 f 拆成两部分,然后一部分直接用 f[j] ,另一部分用集合 A 的一个数来补上就行。因为题目要 B 中所有数的按位与均等于 0 ,所以不会出现有两个数的某一位都是 1 的情况。所以我们在拆 i 的时候可以把 i 中为 1 的位分成两半来计算。为了方便处理,我们把 num[x] 表示 a[j] 中值为 x 的个数, s 表示 i 分出来的一个数, t 为剩余的部分,为了保证不重复计算,要求 s≥ts ,根据乘法原理,则有状态转移方程  。

啊啊啊!没LaTeX好难受!!!!!
可是这样就有一个问题:这些数的和的上限很大,内存装不下啊!不用担心,因为我们选出来的数不会有两位都是1的情况,所以选出来的和自然小于 ,以  为上限即可。

但是这样就会出现一个情况:a[i]=0 算漏了!其实 a[i] 等于 0 时用不用都没关系,根据乘法原理,所以答案要乘上 2^{num0} 。

最后奉上 AC Code
#include<bits/stdc++.h>
using namespace std;
const int NN=300000,P=1e9+7;
int num[NN],phi[NN],f[NN];
bool vis[NN];
int main()
{
    int n,maxx=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        num[x]++;
        maxx=max(maxx,x);
    }
    int m=0;
    while((1<<m)<=maxx)
        m++;
    phi[1]=1;
    for(int i=2;i<=(1<<m);i++)
        if(!vis[i])
            for(int j=i;j<=(1<<m);j+=i)
            {
                vis[j]=true;
                if(!phi[j])
                    phi[j]=j;
                phi[j]=phi[j]/i*(i-1);
            }
    f[0]=1;
    for(int i=1;i<=(1<<m);i++)
        for(int s=i;;s=(s-1)&i)
        {
            int t=i^s;
            if(s<t)
                break;
            f[i]=(f[i]+1ll*f[t]*num[s])%P;
        }
    int ans=0;
    for(int i=0;i<=(1<<m);i++)
        ans=(ans+1ll*f[i]*phi[i+1])%P;
    for(int i=1;i<=num[0];i++)
        ans=ans*2ll%P;
    printf("%d",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值