前置知识:组合数(杨辉三角),高精度(加法)
这道题用高精乘除爆算 C n m C_n^m Cnm 不用杨辉三角也是可以的,不过我们为方便起见统一用杨辉三角。
0x00 理解题意
以样例为例。
3 7
代表什么呢?题目给你了一个长度为 w = 7 w = 7 w=7 的二进制数(01 串),现在从右往左每 k = 3 k = 3 k=3 位一段划分成一个 ⌈ w / k ⌉ \lceil w/k\rceil ⌈w/k⌉ 位的 2 k 2^k 2k 进制数。最高位可能会不完整。
00 000 000
(假设划分成上面这几段)
每一小段都是一个独立的 2 k 2^k 2k 进制位,现在,求出所有 2 2 2 到 ⌈ w / k ⌉ \lceil w/k\rceil ⌈w/k⌉ 位 2 k 2^k 2k 进制数中,每一位的数码都严格小于其右边一位数码的数的个数。
比如这样是合法的:
01 010 011
=Decimal(123)
这样不合法:
01 011 010
=Decimal(132)
注意最高位不能是 0 0 0,也就是不重复。
0x01 组合数
C n m C_n^m Cnm ( n n n 选 m m m) = n ! m ! ( n − m ) ! ={n!\over{m!(n-m)!}} =m!(n−m)!n!。
组合数有一条基本性质 C n m = C n − 1 m − 1 + C n − 1 m C_n^m=C_{n-1}^{m-1}+C_{n-1}^m Cnm=Cn−1m−1+Cn−1m ,跟杨辉三角的递推式是一样的。所以用杨辉三角算组合数有两个好处:1.不容易爆 long long; 2.不用写高精度乘除。空间当然较大,但是本题空间足够了。
注意,根据定义,访问 C[n][m] 的值时,杨辉三角需要从下标 [0][0] 开始递推。
0x02 思路介绍
先不考虑最高位。注意到,在求 n n n 位的合法 2 k 2^k 2k 进制数的时候,相当于在求 C 2 k − 1 n C_{2^k-1}^n C2k−1n 的值。
解释一下:
因为最高位不能是 0 0 0,所以最高位后面的所有位也不可能是 0 0 0 。
然后我们把 1... 2 k − 1 1...2^k-1 1...2k−1 这所有的 2 k − 1 2^k-1 2k−1 个合法数码排成一排,那么每一个长度为 n n n 的组合 C 2 k − 1 n C_{2^k-1}^n C2k−1n(即, 2 k − 1 2^k-1 2k−1 选 n n n)就对应了一个长为 n n n 的 2 k 2^k 2k 进制数,因为组合不讲顺序,所以这样正好和题目要求的 “只保留递增的序列” 一条吻合。
明白了这一点,我们不难写出统计长度从 2 2 2 到 ⌊ w / k ⌋ \lfloor w/k \rfloor ⌊w/k⌋ 的所有合法 2 k 2^k 2k 进制数个数的代码。
scanf("%d %d",&k,&w);
cnt=1<<k;//进制计算
bit=w/k;//完整的位个数
hi=w%k;//最高位的比特数
init(cnt+1);//初始化杨辉三角
hint res(0);//自己定义的高精度类 hint
for(int i=2;i<=bit;i++)
res+=yh[cnt-1][i];//加上完整的位给答案带来的贡献
可表示为
∑
i
=
2
b
i
t
C
2
k
−
1
i
\sum_{i=2}^{bit}C_{2^k-1}^{i}
i=2∑bitC2k−1i
至于最高位的情况也不难。最高位是完整位的情况( h i = 0 hi = 0 hi=0),我们前面已经解决了。
易得,最高位的取值范围是 1 1 1 到 2 h i − 1 2^{hi}-1 2hi−1 共 2 h i − 1 2^{hi}-1 2hi−1 个。
那么次高位(一个完整位)的取值范围就是 2 2 2 到 2 h i 2^{hi} 2hi 。我们知道, 2 k > 2 h i 2^k>2^{hi} 2k>2hi,所以这些数码都是可取的。
原来在求 n n n 位的合法 2 k 2^k 2k 进制数的时候,最高完整位的起始值范围是从 1 1 1 到 2 k − 1 2^k-1 2k−1 ,现在我们相当于把它的值域变成了 2 2 2 到 2 k − 1 2^k-1 2k−1, 3 3 3 到 2 k − 1 2^k-1 2k−1, . . . ... ..., 2 h i 2^{hi} 2hi 到 2 k − 1 2^k-1 2k−1 ,并要求改变了值域后合法 2 k 2^k 2k 进制数的和。
类比前面的方法,把所有的合法数码排成一列求组合,不难得到总和就是
∑
i
=
2
2
h
i
C
2
k
−
i
b
i
t
(
2
k
−
i
>
=
b
i
t
)
\sum_{i=2}^{2^{hi}} C_{2^k-i}^{bit}\ (2^k-i > =bit)
i=2∑2hiC2k−ibit (2k−i>=bit)
代码实现如下(有对式子的轻微改动)
if(hi!=0)
for(int i=1;i<1<<hi;i++)
{
if(cnt-1-i<bit) break;
res+=yh[cnt-1-i][bit];
}
或者可以使用减法原理,直接求值,不用循环:
if(hi!=0)
res+=yh[cnt-1][bit+1],res-=yh[cnt-(1<<hi)][bit+1];
(这个和等于第 bit+1 位完整时的贡献减去第 bit+1 位取了非法值的贡献)
(不完整)AC 代码如下:
#include <bits/stdc++.h>
#include "shadl.cpp"
using namespace std;
typedef high_decimal hint;
hint yh[605][605];
int cnt,bit,hi,k,w;
void init(int x)
{
for(int i=0;i<=x;i++)
{
yh[i][0]=1,yh[i][i]=1;
for(int j=1;j<i;j++)
yh[i][j]=yh[i-1][j-1]+yh[i-1][j];
}
return;
}
int main()
{
scanf("%d %d",&k,&w);
cnt=1<<k;
bit=w/k;
hi=w%k;
init(cnt+1);
hint res(0);
for(int i=2;i<=bit;i++)
res+=yh[cnt-1][i];
// for(int i=1;i<1<<hi;i++)
// {
// if(cnt-1-i<bit) break;
// res+=yh[cnt-1-i][bit];
// }
if(hi!=0)
res+=yh[cnt-1][bit+1],res-=yh[cnt-(1<<hi)][bit+1];
res.output();
return 0;
}
(请手动添加高精度类,这里采用了我自己的实现。)
0x03 总结
典型的组合数题目,难在理解题意和推性质。还要注意高精度的写法。