Solution
用 jio 推柿子,用心卡内存...
先考虑限制条件 3,它等同于选出的数必须小于 (2^w)。如果直接把 (2^w) 转化成 (2^k) 进制,写高精比较麻烦。手模一下转化进制的过程,发现除去最后一次,每次 (\%) 得到的都是 (0)。因此稍微找规律得到:转化为 (2^k) 进制后的位数为 (lfloor { frac{w}{k}} floor + 1),其最高位为 (2^{w \% k}),其余位都是 (0),记为 (n) 位的数 (A)。
现在需要统计严格递增的小于 (A) 的数的个数。分为下面两种情况:
注意:
按照位数讨论,取数时删去 0。如果选择 0,按照递增顺序,它一定在最高位,这种情况将在位数更少时讨论,造成重复。
之所以用组合而不用排列,是因为取出(互不相同的)若干个数字之后,要想按照严格递增顺序,有且仅有一种合法的排列方式。
1. 所有位数 (x) 满足 (1<x<n) 的数
不管怎么取,当前数一定小于 (A)。在除去 0 的 (2^k) 进制中任意取数,方案数为 (C_{2^k - 1}^x)。
2. 所有位数 (x) 满足 (x=n) 的数
一般来说,这种情况需要进行多次讨论,复杂度将会大幅升高。但是本题情况特殊,即除第一位以外后面位都是 0,这个性质也可以看成:位数为 (n) 的数要想比 (A) 小,其最高位一定比 (A) 的最高位小。所以我们枚举其最高位 (i) 满足 (1<i<2^{w\%k}),对于每个 (i),后面的数都有 (C_{2^k-i-1}^{n-1}) 种取法。
组合数可以用 (C_{i,j}=C_{i-1,j}+C_{i-1,j-1}) 这个公式推出,因此以上所有代码实现只需要用到高精加。最后注意,用 int 写高精会炸内存,可以用 string 来写。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 1001;
int k, w, len, base[2333];
int a[2333], b[2333], c[2333];
string C[N][N], Ans = "";
string add(string x, string y)
{
string res = "";
int len1 = x.size(), len2 = y.size(), len3 = max(len1, len2);
memset(c, 0, sizeof(c));
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
for(int i = 0; i < len1; i++) a[i + 1] = x[len1 - i - 1] - '0';
for(int i = 0; i < len2; i++) b[i + 1] = y[len2 - i - 1] - '0';
for(int i = 1; i <= len3; i++)
{
c[i] += a[i] + b[i];
c[i + 1] += c[i] / 10;
c[i] %= 10;
}
while(c[len3 + 1]) len3++;
for(int i = len3; i > 0; i--) res += c[i] + 48;
return res;
}
int main()
{
scanf("%d%d", &k, &w);
len = w / k + 1;
base[0] = 1;
for(int i = 1; i <= 9; i++) base[i] = base[i - 1] * 2;
for(int i = 0; i <= base[9]; i++) // string 类型写高精需要注意初始化
for(int j = 0; j <= base[9]; j++)
C[i][j] = "0";
C[1][1] = "1";
for(int i = 1; i <= base[9]; i++) C[i][0] = "1";
for(int i = 2; i <= base[9]; i++)
for(int j = 1; j <= i; j++)
C[i][j] = add(C[i - 1][j], C[i - 1][j - 1]);
for(int i = 2; i < len; i++) Ans = add(Ans, C[base[k] - 1][i]);
for(int i = 1; i < base[w % k]; i++) Ans = add(Ans, C[base[k] - i - 1][len - 1]);
cout << Ans;
return 0;
}