动态规划--数位dp--二进制状态压缩

和与或

给你一个数组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

2
3 5

输出

15

15

示例2

输入

3 3 3 3

3 
3 3 3

输出

16

16

示例3

输入

2 1 128

2 
1 128

输出

194

194

示例4

输入

4 26 74 25 30

4
26 74 25 30

输出

8409

8409

示例5

输入

2 1000000000 1000000000

2
1000000000 1000000000

输出

420352509

420352509

备注:

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

分析:题目分析,转化

节选自牛客网题解

算了,自己想想怎么组织语言

我们拿几个数去试试,比如4+0=4|0,4+3=4|3,等等,可以发现,全部看成二进制数加法和取或,那么求和时候不会出现进位。下面,如此分析,求和时候如果记数组a的所有元素的和是sum,那么,对于sum二进制表示的各位数,sum二进制表示是---------------,每个横线表示一个数(0或者1),对于第p位数,如果该数是1,那么a中所有的元素,有且只能有一个元素第p位都要是1,其他的都要是0;如果该数是0,那么a中所有元素的第p位都要是0.

本题求的是满足和等于或且分别小于另一个数组对应位置元素等条件的数组a的个数,我们可以转化成,满足一个数sum,将其各个位分给数组a,且如果分的那一位上数字是1的话,只分给数组a的一个元素的相应数位上(保证不进位),同时注意到,只分给数组a的一个元素的那个元素,是有条件的,首先,那个数位上的1分给a中的那个元素,要满足小于数组r中相应元素的条件,a中有的元素有的只能分到0,而不能分到1(如上述,分到1就不满足小于r中对应元素的限制了),所以,我们很关注怎么描述这种限制。

对于上面的限制,我们需要仔细分析

对于数sum(a数组元素的和),我们考察sum的第p位,同时,由上面的分析,数组a的元素的第p位在求和时不能产生进位,所以我们也要考察数组a所有元素的第p位,进一步,我们限制条件里有数组r,所以我们也需要考虑数组r所有元素的第p位,另外,我们不要忘了,当前状态下(动态规划都是有状态的,可以认为是当前这一种情况下)数组a各个元素的限制状态的情况是什么,我们用int变量limit存储,limit的二进制表示的第i位是1,表示当前情况下数组a中元素a[i]的第p位没有限制(无论取1还是取0都不会使a[i]的值超过对应的r[i]),看到这里,可能会想,对于第p位,limit都存储了当前的数组a各个元素的限制,还要数组r干什么,数组r存储的数是数组a各个元素的最大值,用于求解数sum的第p-1位时数组a各个元素的限制。

******************************************************************************************************

写到这里,觉得我写的还是不是很好理解,还是贴上牛客上的题解:

分析 :题目要求 A1+A2+A3…+AN=A1|A2|A3…|AN ,把|运算符用+号来表示,A1|A2=A1+A2-2*(A1&A2),可以推出,把A1-AN转化成二进制表达后,同一位二进制位上不能有两个以上的1,不然一定会出现式子里的减法从而小于A1+A2…+AN,我们不妨设SUM为所有A数组的和,然后把SUM转化成二进制,条件就被我们转化成,SUM二进制上的 1 只会来源于N个Ai中的一个,根据这个设计DP的状态表示。

状态表示:DP[iI[j](i=62,j=1<<12)表示枚举到sum二进制的第i位时,R数组里各个数字的限制情况,这里特别解释下限制的意思,因为我们枚举SUM二进制时要考虑二进制上1的来源,设R1=10010(二进制),如果SUM的第五位是由A1的数字贡献的,既A1>=10000,那么当我们继续考虑第四位的1时,A1必然无法继续作出贡献,会导致A1超出R1的限制,直到枚举至第二位,它本身上界有1时,才能继续贡献,可是如果之前第5位的时候A1不贡献1,那么根据二进制,高位的1大于所有比它低位的1的和(10000>01111),既A1<10000,那么在考虑后面4,3,2,1位的时候,A1可以任意选择贡献1还是不贡献1,这就是限制与不限制的区别。至此J的含义已经清楚 J可以被当成一个12位的二进制数,如果第一位上是1则代表,A1是不受限制的,反之则受限,受限的Ai只能在其上届Ri有1的时候才能作出贡献。

转移方程:假设枚举到第I位,限制状态是J,如果SUM该位二进制数取0,则在该位上是1的R[i],在之后的搜索中全部变成无限制,而如果取1,1的来源有两种情况,一种是R[I]被限制了,但这一位上有1,那么R[I]依旧保持限制,其他这一位上有1的变为无限制,第二种是R[I]没被限制,那么R[I]保持无限制,其他这一位上有1的也为无限制。当然之前就已经无限制的数字在之后依旧无限制,总计三种情况,记忆化搜索。

 借鉴的题解如上

*************************************************************************************************************

限制的解除实际上就是这里的难点

我们定义int dfs(int p,int limit),这个函数是干什么的,对于编写的函数,我们有名字,有返回值,有参数,最重要的是,理解函数的功能,该函数的功能是,假设对于数sum(我们要求的就是所有满足条件的数组a的个数,就是把数sum各位分配到合法数组a各个元素上的方案数) ,我们已经分配好了第最高位到第p+1位上的数至数组a各个元素的第最高位至第p+1位,现在,我们需要分配sum数的第p位到数组a各个元素的第p位,此时数组a的个元素的第p位限制情况以二进制形式存储在数limit中(就是说,limit的二进制表示的第i位数表示数组a中元素a[i]第p位的约束情况,limit的二进制表示的第i位数是0,表示数组a中元素a[i]第p位的受到约束,就是说这此时是有限制的,如果a[i]第p位取1(当然要能取1才取1),那么a[i]的第p-1位就不能随便取(限制继续传下去了),否则可能超过r[i]),对于其他的a[j],j不等于i,那么由于a数组只能有一个元素第p位取1,所以a[j]的第p位实际上取0,所以a[j]的第p-1位可以随意取值,不受限制;limit的二进制表示的第i位数是1,表示数组a中元素a[i]第p位的不受约束,就是说a[i]第p位取1也不会超过r[i],可以随意取,那么a[i]的第p-1位也不受限制(不受限制继续传下去了),当前函数是从第p位开始分配sum的数位,直到分配到第0位,所有满足条件的数组a的个数和。

对于sum的第p位,可以取0可以取1,总个数是取0和取1的加和。

当sum第p位取0时,不管数组a各元素有无限制,反正都是只能取0,那么下一位(第p-位上)如果r[i]的第p位是1,相应第p-1位时,a[i]的第p-1位不受限制,反正不会超过r[i],所以下次第p-1位,解除r[i]第p位是1的元素a[i]的限制

当sum第p位取1时,当a[i]有限制时,如果r[i]第p位是0,显然sum第p位的1不能由这个a[i]来贡献了;当a[i]有限制时,如果r[i]第p位是1,sum第p位的1能由这个a[i]来贡献,且这种限制仍然传递到第p-1位上a[i]的限制(不限制就可能超过r[i]),其他的a[j],j不等于i,如果r[j]的第p位是1,那么下一位第p-1位a[j]解除限制;当a[i]无限制时,那么第p-1位a[i]依然无限制,其他的a[j],如果本来有限制,但是r[j]的第p位是1,则第p-1位时a[j]解除限制。

当sum第p位取1时,当a[i]有限制时,如果r[i]第p位是0,显然sum第p位的1不能由这个a[i]来贡献了;当a[i]有限制时,如果r[i]第p位是1,sum第p位的1能由这个a[i]来贡献,且这种限制仍然传递到第p-1位上a[i]的限制(不限制就可能超过r[i]),其他的a[j],j不等于i,如果r[j]的第p位是1,那么下一位第p-1位a[j]解除限制;当a[i]无限制时,那么第p-1位a[i]依然无限制,其他的a[j],如果本来有限制,但是r[j]的第p位是1,则第p-1位时a[j]解除限制。

可以看出,dfs(p,limit)探讨的是,从第p位开始分配sum数至数组a的元素中,这里所有的基本操作都是建立在数的第p位的基础上的,limit说的也是当前第p位上a数组元素的限制,(认为从第最高位开始到第p+1位都已经分配好了,dp[i][j]表示的是,从第p位开始分配sum数,且数组a的限制情况存储在j中,的所有满足情况的数组a的个数

不要忘了取余,不要忘了1l<<p, (long long 1<<p)是先将1强制转换为long long,再左移p位

#include<iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
const int mod = 1e9+9;

int dp[63][1 << 12];
long long R[12];
int n;
int dfs(int p, int limit) {
    if (p < 0)return 1;
    if (dp[p][limit] != -1)return dp[p][limit];
    int ans = 0;//记录r元素第p位上是否有1
    for (int i = 1; i <= n; i++)
        if ( R[i] & ((long long)1<<p) )//取出r中每个元素的第p位的值
            ans |= (1 << i);
    long long res = 0;
    //ans分类讨论,sum的第p位可以取0可以取1,两种情况加和
    //sum第p位取0的话直接进入sum第p-1位的求解讨论,解除限制
    //sum第p位取1的话,需要看每个r元素是否受限,没有受限,该元素仍不受限且解除其他受限的r元素
    //***********************************受限,该元素仍受限,解除其他元素受限
    res += dfs(p - 1, ans|limit  );//sum第p位取0
    for (int i = 1; i <= n; i++)//sum第p位取1
    {
        if ((1 << i) & limit)
            res += dfs(p - 1, ans| limit );
        else
            if (!((1<<i)&limit)&&ans & (1 << i))
                res += dfs(p - 1, (ans ^ (1 << i)) | (limit));
    }
    dp[p][limit] = res % mod;
    return dp[p][limit];
}
int main() {
    memset(dp, -1, sizeof(dp));
    cin >> n;
    for (int i = 1; i <= n; i++)
        scanf("%lld", &R[i]);
    dfs(61, 0);
    cout << dp[61][0] << endl;
    return 0;
}

另附一个链接数位dp总结 之 从入门到模板_wust_wenhao的博客-CSDN博客_数位dp模板for(int i=le;i<=ri;i++) if(right(i)) ans++;基础篇数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数。所谓数位dp,字面意思就是在数位上进行dp咯。数位还算是比较好听的名字,数位的含义:一个数有个位、十位、百位、千位......数的每一位就是数位啦!之所以要引入数位的概念完全就是为了dp。https://blog.csdn.net/wust_zzwh/article/details/52100392 

补充一句,注意到前缀和,注意到该数位dp中说的solve(re)-solve(le-1),和前缀和类似,标记一下

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值