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 ",solve(f)-solve(s-1)); 44 return 0; 45 }
关于结果的存储,不同的想法具体的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 ",solve(m)-solve(n-1)); 65 return 0; 66 }