牛客-和与或

题目描述

给你一个数组R,包含N个元素,求有多少满足条件的序列A使得
0 ≤ A[i] ≤ R[i]
A[0]+A[1]+...+A[N-1]=A[0] or A[1]... or A[N-1]
输出答案对1e9+9取模

输入描述:

第一行输入一个整数N (2 ≤ N ≤ 10)
第二行输入N个整数 R[i] (1 ≤ R[i] ≤ 1e18)

输出描述:

输出一个整数

示例1

输入

2
3 5

输出 

15

示例2 

输入


3 3 3

输出 

16

示例3

输入 


1 128

输出 

194

示例4 

输入

4
26 74 25 30

输出 

8409

示例5

 输入

2
1000000000 1000000000

输出 

420352509

备注: 

子任务1: n <= 3
子任务2: n <= 5
子任务3: 无限制

 解题思路

这道题目的突破口是由A[0]+A[1]+...+A[N-1]=A[0] or A[1]... or A[N-1]所推出来的条件,即把A[i]全部相加得到的和sum用二进制来表示,sum二进制上的任何一位的1,都只能由数组A[i]中的某一位提供

我们就可以遍历sum二进制的每一位,根据另外一个限制条件0 ≤ A[i] ≤ R[i]这个可以用状态压缩来表示,即一个数的二进制其第i位上的数来表示A[i]是否被限制,0表示限制,1表示不受限制(限制的意思是只有当R[i]的当前位上有1时才可以取1,不受限制就取1和0都可以),来判断其每一位数字能否取1,以及取1时可能由那个A[i]提供,由此得到序列A的数量

此时分为三种情况,第一种,当前位取0,则当前位取1且受限制的全部变为不受限制

第二种,当前位取1且受限制,则除自己继续保持受限制以外,其他位取1且受限制的全部变为不受限制

第三种,当前位取1且不受限制,则当前位取1且受限制的全部变为不受限制

限制与不受限制:设R0=10011(二进制),如果SUM的第五位上取1且由A0贡献,既A0>=10000,那么当我们第四位取1时,A0由于0 ≤ A[i] ≤ R[i]无法继续贡献,直到枚举至第二位,R0有1时,才能继续贡献,如果之前第5位的时候A0不贡献1,既A0<10000,那么在考虑后面4,3,2,1位的时候,A0可以任意选择贡献1还是不贡献1,这就是两者的区别

用数组dp[i][j]来表示在sum二进制的第i位时,其A[i]的限制状态为j时,序列A可能的数量

核心代码

#include <bits/stdc++.h>
using namespace std;
long long dp[66][1<<12];///dp[i][j]表示在二进制的第i位,且限制状态为j的情况下,序列A的可能数量,0代表受限,1代表不受限
int N;
long long R[11];
typedef long long ll;
const int mod=1e9+9;

int dfs(int a,int b){///探索在二进制的第a位,且限制状态为b的情况下,序列A的可能数量
    if(a<0)return 1;///这是从a=0得来
    if(dp[a][b]!=0)return dp[a][b];
    int ans=0;
    for(int i=0;i<N;i++){///把当前位上有1的记录下来
        if(((ll)1<<a)&R[i])ans=ans|(1<<i);}
    long long sum=0;///分情况讨论
    sum+=dfs(a-1,b|ans);///取0
    for(int i=0;i<N;i++){
        if(((ll)1<<a)&R[i] && !((1<<i)&b))///取1且受限
            sum+=dfs(a-1,(b|ans)-(1<<i));
        else if((1<<i)&b)///取1不受限
            sum+=dfs(a-1,b|ans);
    }
    dp[a][b]=sum%mod;
    return dp[a][b];}

需要注意的细节是,当我们运算1<<a时,由于a可能会比较大,如果不把1设置成long long类型可能会溢出

题解代码

#include <bits/stdc++.h>
using namespace std;
long long dp[66][1<<12];///dp[i][j]表示在二进制的第i位,且限制状态为j的情况下,序列A的可能数量,0代表受限,1代表不受限
int N;
long long R[11];
typedef long long ll;
const int mod=1e9+9;

int dfs(int a,int b){///探索在二进制的第a位,且限制状态为b的情况下,序列A的可能数量
    if(a<0)return 1;///这是从a=0得来
    if(dp[a][b]!=0)return dp[a][b];
    int ans=0;
    for(int i=0;i<N;i++){///把当前位上有1的记录下来
        if(((ll)1<<a)&R[i])ans=ans|(1<<i);}
    long long sum=0;///分情况讨论
    sum+=dfs(a-1,b|ans);///取0
    for(int i=0;i<N;i++){
        if(((ll)1<<a)&R[i] && !((1<<i)&b))///取1且受限
            sum+=dfs(a-1,(b|ans)-(1<<i));
        else if((1<<i)&b)///取1不受限
            sum+=dfs(a-1,b|ans);
    }
    dp[a][b]=sum%mod;
    return dp[a][b];}

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    ///关闭输入输出流
    cin>>N;
    for(int i=0;i<N;i++)cin>>R[i];
    dfs(63,0);///sum最长有64位二进制
    cout<<dp[63][0]<<endl;}

要是有看不懂的地方,可以给我发信息,我看到了就会回复。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值