二进制上的数位dpPOJ 3252

Round number POJ - 3252

  题目大意:一个"round number" 数的定义是,将它转化成2进制后,0的个数大于等于1的个数,要求的就是在[s,f]范围内"round number"的个数

  和之前的数位dp不同的是,这题是对二进制的数位进行dp,所以就存在着一个限制,前面有没有存在1,如果前面没有1.那么像00001,就是1,前面的0是不计数的,所以重点就在于考虑这个dfs时就得分前面有没有1,如果没有那么0就不计数,反之我们记录0和1的差值,取0加1,取1减1,不过需要注意的是,差值可能是负数,所以要转正数,详情见代码

 1 #include<cstdio>
 2 #include<cstring>
 3 #define ll long long
 4 const int N=50;
 5 int up[N];
 6 ll n,s,f,dp[N][2*N][2];//dp[i][j][k]代表当前为第i位,前面0和1的差值为j-N,前面有没有1的结果
 7 //例如dp[4][50][1]就代表第4位,在第4位前面0和1差值为0,并且前面有1的结果数 
 8 ll dfs(int p,int d,bool is1,bool isu)//p 位数,d 0和1的差值,is1 该位前面有没有1 isu有没有上限 
 9 {
10     if(!p)//边缘状态,0大于等于1(d>=0)返回1,反之返回0 
11         return d>=0;
12     if(-d>p)//小剪枝,如果剩下位数全是0加起来都没有1多,返回0 
13         return 0;
14     if(!isu&&dp[p][d+N][is1]!=-1)
15         return dp[p][d+N][is1];
16     ll ans=0;
17     for(int i=0;i<=(isu ? up[p] : 1);i++)
18     {
19         if(is1)//当前面有1正常记录0的差值 
20             ans+=dfs(p-1,(i ? d-1 : d+1),is1||i,isu&&i==up[p]);
21         else//当前面没有1,0不计,1记-1 
22             ans+=dfs(p-1,-i,is1||i,isu&&i==up[p]);
23     }
24     if(!isu)
25         dp[p][d+N][is1]=ans;
26     return ans;
27 }
28 ll solve(ll x)
29 {
30     int num=0;
31     while(x)
32     {
33         up[++num]=x&1;
34         x>>=1;
35     }
36     return dfs(num,0,0,1);
37 }
38 int main()
39 {
40     ll s,f;
41     memset(dp,-1,sizeof(dp));
42     while(~scanf("%lld%lld",&s,&f))
43         printf("%lld\n",solve(f)-solve(s-1));
44     return 0;
45 }
RNGRNGRNG

  关于结果的存储,不同的想法具体的dp还有也不同,有些是dp[i][j][k]是当前是第i位,前面0的个数为j,前面1的个数为k的结果有多少个,不过大体的思路还是相同的

  除数位dp外,还有一个组合数学的解法,不过相比大佬们,我的思路有点麻烦,详情见代码

 1 #include<cstdio>
 2 #include<cstring>
 3 #define ll long long
 4 ll n,m,c[36][36]={1},a[36]={0};//c组合数,a[i]长度为i,首位1在第i位存在RN的个数 
 5 //比加a[1]保存1,a[2]保存10,11,a[3]保存100,101,110,111,中满足RN的个数
 6 //所以a[1]=0,a[2]=1,a[3]=1 
 7 int up[36]={1};
 8 void init()
 9 {
10     for(int i=1;i<=32;i++)
11     {
12         c[i][0]=1;
13         for(int j=1;j<=i;j++)
14         {
15             if(j<=i/2)
16                 c[i][j]=c[i-1][j-1]+c[i-1][j];
17             else
18                 c[i][j]=c[i][i-j];
19         }
20         for(int j=i-1;j>=(i+1)/2;j--)//首位i是1,剩下i-1位0可以i-1个,
21         //i-2个,假设最少取j个满足要求,那么j>=(i+1)/2(0的数目大于等于总数目的一半) 
22             a[i]+=c[i-1][j];
23     }
24 }
25 ll solve(ll x)
26 {
27     int num=0;
28     while(x)
29     {
30         up[++num]=x&1;
31         x>>=1;
32     }
33     ll ans=0;
34     for(int i=1;i<num;i++)
35         ans+=a[i];
36     //假如x转化成2进制是1101
37     //先算上1,10~11,100~111的里的答案 
38     if(num>1)//先把1000算上 
39         ans++;
40     int z=0;//记录第i位前0的个数 
41     //再来计算1000~1101之间的答案,down代表有没有计算到下界
42     //一开始的下界就是1000                                   
43     for(int i=num-1,down=1;i>=1;i--)
44     {
45         if(up[i])//当存在1时,例如第二位就是1,我们就求的是1000~1100的答案 
46         {
47             if(down)//因为之前已经算过1000了,所以这里减去
48                 ans--,down=0;
49             for(int j=i-1;z+j+1>=(num+1)/2&&j>=0;j--)//假设第i位是0,前面的0加上
50             //第i位再加上后面取的j个0,总的0的个数要大于等于总个数 
51                 ans+=c[i-1][j];
52             if(i-1+z>=(num+1)/2)//假设后面的i-1全是0,0的个数大于等于总个数 
53                 ans++,down=1;//那么1100可以作为下界 
54         }
55         else
56             z++;
57     }
58     return ans;
59 }
60 int main()
61 {
62     init();
63     while(~scanf("%lld%lld",&n,&m))
64         printf("%lld\n",solve(m)-solve(n-1));
65     return 0;
66 }
WEWEWE

 

posted @ 2019-03-01 18:35  新之守护者  阅读(323)  评论(0编辑  收藏  举报