P1066 2^k进制数
题目描述
设 r 是个 2^k进制数,并满足以下条件:
- r 至少是个 2 位的 2^k进制数。
- 作为 2^k进制数,除最后一位外,r的每一位严格小于它右边相邻的那一位。
- 将 r转换为二进制数 q后,则 q的总位数不超过 w。
在这里,正整数 k,w 是事先给定的。
问:满足上述条件的不同的 r共有多少个?
我们再从另一角度作些解释:设 S是长度为 w 的 0101 字符串(即字符串 S 由 w 个 0 或 1 组成),S对应于上述条件三中的 q。将 S从右起划分为若干个长度为 k的段,每段对应一位 2^k 进制的数,如果 S至少可分成 2段,则 S所对应的二进制数又可以转换为上述的 2^k进制数 r。
例:设 k=3,w=7 k =3。则 r 是个八进制数( 2^3=8 )。由于 w=7,长度为 7 的 01 字符串按 33位一段分,可分为 3 段(即 1,3,3,左边第一段只有一个二进制位),则满足条件的八进制数有:
2 位数:
高位为 1:6 个(即 12,13,14,15,16,17 ),
高位为 2:5 个,
…,
高位为 6:1个(即 67 )。
共 6+5+…+1=21 个。
3位数:
高位只能是 1,
第 2位为 2:5 个(即 123,124,125,126,127),
第 2 位为 3:4 个,
…,
第 2位为 6:1个(即 167)。
共 5+4+…+1=15 个。
所以,满足要求的 r共有 36 个。
输入格式
一行两个正整数 k,w 用一个空格隔开:
输出格式
一行一个个正整数,为所求的计算结果。
即满足条件的不同的 r的个数(用十进制数表示),要求不得有前导零,各数字之间不得插入数字以外的其他字符(例如空格、换行符、逗号等)。
(提示:作为结果的正整数可能很大,但不会超过 200 位)
解析
首先我们来理解题意,r为k进制数,转化为二进制后,01串最多位不能超过w。由于k位01串可以组成一个k进制数,我们不妨将最多位可能的情况下的w按k位一组进行切分,得到L = ceil(w/k)组,那么r在k进制情况下最多有L位,最少有2位。
例如输入3,7时,r位8进制数,r的每位由3个二进制位构成,r最多情况下可能由7个二进制位构成。我们将7按3位一组进行切分,得到[1,3,3]。
r在k进制下最少只有两位,最多L位,则r的所有情况为 2位r下的情况到L位r下的情况总数之和。
以 3 7为例,则r可能有2位或3位,当r两位的时候,可能情况有 12,13,14,..17,23,24..27,67,即在1..7的数字中选择2个,构成二位的r。当r三位时,即首先选择出第一位,并在剩下的位中选择出2个比第一位大的数。
由此,可以看出这个问题实际上是组合数问题。
当前n个分段,每个分段均为k位时,则每段可选择的数字为2k-1,而r的可能情况为(2k-1)中取i位(i从2到n)
最后一个分段有可能不到k位,则首先从1开始选择,做出第一次选择后,计算出剩下n-1个分段的组合数,并依次求和,得到最终结果。
接下来问题就转换到如何计算组合数了。
计算组合数可以参考这篇博文 参考链接
此处使用杨辉三角递推求解组合数值。
最后本题中会出现大整数,超出 long long int范围,因此对于C++/C实现来说需要手写大整数
在此首先给出Java版本代码
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
private static final int n = 1000;
static BigInteger [][]comb = new BigInteger[1000][1000];
static void init()
{
for(int i=0;i<n ;i++)
{
comb[i][0] = BigInteger.valueOf(1);
comb[i][i] = BigInteger.valueOf(1);
for(int j=1;j<i;j++)
{
comb[i][j] = comb[i-1][j].add(comb[i-1][j-1]);
}
}
}
static BigInteger C(int n, int m)
{
if(n<m) return BigInteger.valueOf(0);
return comb[n][m];
}
public static void main(String[] args) {
BigInteger cnt =BigInteger.valueOf(0);
init();
int k,w;
Scanner in = new Scanner(System.in);
k = in.nextInt();
w = in.nextInt();
int fullPart = w / k;
int divPart = (int) Math.ceil(1.0*w / k);
int resPart = 0;
if (w % k != 0)
{
resPart = (int) (w - k * fullPart);
}
int sel = (int) (Math.pow(2, k) - 1);
for (int i = 2; i <= divPart; i++)
{
if (i != divPart)
{
cnt = cnt.add(C(sel,i));
}
else
{
if (resPart != 0)
{
int t = (int) (Math.pow(2, resPart) - 1);
for (int j = 1; j <= t; j++)
{
cnt = cnt.add(C(sel-j,i-1));
}
}
else
{
cnt = cnt.add(C(sel,i));
}
}
}
System.out.println(cnt);
}
}
C语言代码 此处大整数代码来自于其他博文,一时找不到出处
#include<stdio.h>
#include<string.h>
#include<math.h>
#define ll long long int
#define maxChar 130
#define maxlen 260
typedef struct HP
{int len;
char s[maxChar];
}HP;
void PrintHP(HP x) {for(int i=x.len;i>=1;i--) printf("%d",x.s[i]);}
void Str2HP(const char *s,HP *x)
{
x->len=strlen(s);
for(int i=1;i<=x->len;i++) x->s[i]=s[x->len-i]-'0'; //倒着记录
}
void Plus(const HP a,const HP b,HP*c)
{
int i;c->s[1]=0;
for(i=1;i<=a.len||i<=b.len||c->s[i];i++){
if(i<=a.len) c->s[i]+=a.s[i];
if(i<=b.len) c->s[i]+=b.s[i];
c->s[i+1]=c->s[i]/10;c->s[i]%=10;
}
c->len=i-1;if(c->len==0) c->len=1;
}
HP comb[maxlen][maxlen];
void init()
{
for(int i=0;i<maxlen ;i++)
{
Str2HP("1",&comb[i][0]);
Str2HP("1",&comb[i][i]);
for(int j=1;j<i;j++)
{
Plus(comb[i-1][j],comb[i-1][j-1],&comb[i][j]);
}
}
}
HP C(ll n, ll m)
{
HP t;
Str2HP("0",&t);
if(n<m) return t;
return comb[n][m];
}
int main()
{
HP ans;
HP temp;
ll cnt = 0;
Str2HP("0",&ans);
ll k, w;
init();
scanf("%lld %lld", &k, &w);
ll fullPart = w / k;
ll divPart = ceil(1.0*w / k);
int resPart = 0;
if (w % k != 0)
resPart = w - k * fullPart;
int sel = pow(2, k) - 1;
for (int i = 2; i <= divPart; i++)
{
if (i != divPart)
{
HP t= C(sel,i);
HP a = ans;
Plus(a,t,&ans);
}
else
{
if (resPart != 0)
{
int t = pow(2, resPart) - 1;
for (int j = 1; j <= t; j++)
{
HP x = C(sel - j, (i - 1));
HP a = ans;
Plus(a,x,&ans);
}
}
else
{
HP x = C(sel, i);
HP a = ans;
Plus(a,x,&ans);
}
}
}
PrintHP(ans);
}