把二进制长度写成只与位数 m 和最高位有关的 k(m-1)+bitlen(d1),固定两者后低位是纯组合选择,再按最高位的二进制位数分段用 hockey-stick 恒等式求和。
OJ: roj
题目 ID: 3551
难度:提高
标签:数学组合计数进制NOIPpython
创建: 2026-10-02 07:01
更新: 2026-10-10 11:30
形式化题目
给定正整数
至少是个 2 位数; - 从左到右每一位严格小于右边相邻的一位,且最高位不为
; - 把
看成二进制数时,总位数不超过 (即 )。
题面用“把长度
样例:输入 3 7,输出 36。按
- 2 位数:最高位为 1 时低位可取
共 个( ),最高位为 2 时 个,……,最高位为 6 时 个( ),共 个。 - 3 位数:低位两段占
个二进制位,再加上最高位 自己的位数,总长不能超过 ,故 只能占 位即 (长度公式的完整推导见下文正解);第二位为 2 时第三位可取 共 5 个( ),为 3 时 4 个,……,为 6 时 1 个( ),共 个。 - 4 位数:仅低位就要
位,加上最高位至少 1 位即 ,不可能,故答案 。
正解
思路
朴素做法及其瓶颈。 最直接的思路是枚举所有长度
关键观察:长度只由位数和最高位决定。 设
后半部分只占据低
也就是说,长度条件
固定
按最高位的位数分段求和。 长度条件对同一位数
其中约定
把样例
| 长度 |
是否 |
该格贡献 |
||
|---|---|---|---|---|
| 2 | 1( |
4 | ✓ | |
| 2 | 2( |
5 | ✓ | |
| 2 | 3( |
6 | ✓ | |
| 3 | 1( |
7 | ✓ | |
| 3 | 2( |
8 | ✗ | 不计 |
| 4 | — | ✗ | 不存在 4 位数 |
观察重点:表格第一行(
代码
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// 十进制大整数(little-endian),只做 3551 需要的加法/减法/乘法/整除小整数
struct BigInt {
vector<int> d;
BigInt() { d.push_back(0); }
BigInt(ll x) { d.clear(); if (x == 0) d.push_back(0); else while (x) { d.push_back((int)(x % 10)); x /= 10; } }
void trim() { while (d.size() > 1 && d.back() == 0) d.pop_back(); }
bool is_zero() const { return d.size() == 1 && d[0] == 0; }
int cmp(const BigInt& o) const {
if (d.size() != o.d.size()) return d.size() < o.d.size() ? -1 : 1;
for (int i = (int)d.size() - 1; i >= 0; i--)
if (d[i] != o.d[i]) return d[i] < o.d[i] ? -1 : 1;
return 0;
}
string str() const { string r; for (int i = (int)d.size() - 1; i >= 0; i--) r += char('0' + d[i]); return r; }
BigInt operator+(const BigInt& o) const {
BigInt r; r.d.assign(max(d.size(), o.d.size()) + 1, 0);
for (size_t i = 0; i < d.size(); i++) r.d[i] += d[i];
for (size_t i = 0; i < o.d.size(); i++) r.d[i] += o.d[i];
for (size_t i = 0; i + 1 < r.d.size(); i++) { r.d[i + 1] += r.d[i] / 10; r.d[i] %= 10; }
r.trim(); return r;
}
BigInt operator-(const BigInt& o) const { // 假设 *this >= o
BigInt r; r.d = d;
for (size_t i = 0; i < o.d.size(); i++) {
r.d[i] -= o.d[i];
if (r.d[i] < 0) { r.d[i] += 10; r.d[i + 1]--; }
}
for (size_t i = 0; i + 1 < r.d.size(); i++)
if (r.d[i] < 0) { r.d[i] += 10; r.d[i + 1]--; }
r.trim(); return r;
}
BigInt operator*(const BigInt& o) const {
BigInt r; r.d.assign(d.size() + o.d.size(), 0);
for (size_t i = 0; i < d.size(); i++)
for (size_t j = 0; j < o.d.size(); j++)
r.d[i + j] += d[i] * o.d[j];
for (size_t i = 0; i + 1 < r.d.size(); i++) { r.d[i + 1] += r.d[i] / 10; r.d[i] %= 10; }
r.trim(); return r;
}
BigInt div_small(ll x) const {
BigInt r; r.d.assign(d.size(), 0);
ll rem = 0;
for (int i = (int)d.size() - 1; i >= 0; i--) {
ll cur = rem * 10 + d[i];
r.d[i] = (int)(cur / x); rem = cur % x;
}
r.trim(); return r;
}
};
// 组合数 C(n, k),结果用 BigInt
BigInt C[520][520];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int k, w;
cin >> k >> w;
int B = 1 << k;
// 组合数打表
for (int i = 0; i <= B; i++) {
C[i][0] = BigInt(1);
for (int j = 1; j <= i; j++)
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
}
// 位数 m 的上界
int m_max = min(B - 1, (w - 1) / k + 1);
BigInt ans(0);
for (int m = 2; m <= m_max; m++) {
for (int j = 1; j <= min(k, w - k * (m - 1)); j++) {
// C(B - 2^(j-1), m) - C(B - 2^j, m)
BigInt a = C[B - (1 << (j - 1))][m];
BigInt b = C[B - (1 << j)][m];
ans = ans + (a - b);
}
}
cout << ans.str() << "\n";
return 0;
}#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-02 07:05
# update_at: 2026-10-02 07:05
import sys
from math import comb
def count_rising(k: int, w: int) -> int:
"""数出二进制长度不超过 w 的、2^k 进制下位数严格递增且至少两位的数的个数。
r 有 m 位(最高位为 a)时,二进制长度 = k*(m-1) + a 的位数,因为 r 的最高位
就是最高位数字 a 所占的那 k 位里的最高有效位。
固定 m 与最高位 a:其余 m-1 位必须从比 a 大的数字 {a+1..B-1} 里选,共
C(B-1-a, m-1) 种。按 a 的位数 j 分段(a 落在 [2^(j-1), 2^j-1]),对
C(B-1-a, m-1) 用 hockey-stick 公式求和即 C(B-2^(j-1), m) - C(B-2^j, m)。
"""
B = 1 << k # 2^k 进制的基数
# 位数 m 的上界:k*(m-1) 位加上最高位至少 1 位不得超过 w;数字取自 1..B-1 故 m<=B-1
m_max = min(B - 1, (w - 1) // k + 1)
return sum(
comb(B - (1 << (j - 1)), m) - comb(B - (1 << j), m) # 最高位落在 [2^(j-1), 2^j-1]
for m in range(2, m_max + 1)
for j in range(1, min(k, w - k * (m - 1)) + 1) # 长度约束:k*(m-1)+j <= w
)
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
k, w = next(data), next(data)
print(count_rising(k, w))
if __name__ == "__main__":
solve()复杂度
位数 math.comb(参数不超过
总结
- 先定位长度条件的自由度:
,把指数级的"逐序列验证"降维成对 的二维求和,这是本题从暴力到正解的全部关键。 - 递增序列 = 子集:固定最高位后低位只是"从更大的数里选
个",是纯组合数;再按最高位的二进制位数分段,hockey-stick 恒等式把段内求和化成两个组合数之差。 - 边界由约定自然吸收:
range(2, ...)保证至少两位,从 1 开始保证最高位非 0,内层上限 时该位数整段跳过, math.comb对返回 0。 - 复杂度
与最优解同阶;结果可能很大但不超过 200 位,Python 大整数直接输出十进制即可。