题目描述
200320032003 年板球世界杯正在南非举行,组委会希望根据球员在世界杯中的表现制定一个新的排名系统。排名系统包含 KKK 个不同的部门(例如击球、防守等),每个部门 iii 的最高得分有一个范围 [Li,Ui][L_i, U_i][Li,Ui],且所有部门的最高得分之和必须恰好等于 NNN 。需要计算有多少种不同的分配方案。
问题分析
问题本质
这是一个有上下界的整数划分问题。设第 iii 个部门的最高得分为 xix_ixi ,则:
- Li≤xi≤UiL_i \le x_i \le U_iLi≤xi≤Ui
- ∑i=1Kxi=N\sum_{i=1}^{K} x_i = N∑i=1Kxi=N
求满足条件的整数解 (x1,x2,…,xK)(x_1, x_2, \dots, x_K)(x1,x2,…,xK) 的个数。
变量代换
令 yi=xi−Liy_i = x_i - L_iyi=xi−Li ,则 0≤yi≤Ui−Li0 \le y_i \le U_i - L_i0≤yi≤Ui−Li 。设 Ri=Ui−LiR_i = U_i - L_iRi=Ui−Li ,则约束变为:
- 0≤yi≤Ri0 \le y_i \le R_i0≤yi≤Ri
- ∑i=1Kyi=S\sum_{i=1}^{K} y_i = S∑i=1Kyi=S ,其中 S=N−∑i=1KLiS = N - \sum_{i=1}^{K} L_iS=N−∑i=1KLi
无解判断
若 S<0S < 0S<0 或 ∑i=1KUi<N\sum_{i=1}^{K} U_i < N∑i=1KUi<N ,则无解,输出 000 。
容斥原理
这是一个有上界的整数解个数问题,可以用容斥原理求解。
首先,不考虑上界 yi≤Riy_i \le R_iyi≤Ri ,只考虑 yi≥0y_i \ge 0yi≥0 且 ∑yi=S\sum y_i = S∑yi=S 的非负整数解个数为:
(S+K−1K−1) \binom{S + K - 1}{K - 1} (K−1S+K−1)
这是因为在 KKK 个变量中分配 SSS 个不可区分的球,相当于在 S+K−1S + K - 1S+K−1 个位置中选择 K−1K - 1K−1 个隔板。
接下来,考虑至少有一个变量超过上界的情况。对于子集 T⊆{1,2,…,K}T \subseteq \{1, 2, \dots, K\}T⊆{1,2,…,K} ,令 TTT 中的变量都满足 yi≥Ri+1y_i \ge R_i + 1yi≥Ri+1 。令 zi=yi−(Ri+1)z_i = y_i - (R_i + 1)zi=yi−(Ri+1) ,则 zi≥0z_i \ge 0zi≥0 ,且:
∑i=1Kzi=S−∑i∈T(Ri+1) \sum_{i=1}^{K} z_i = S - \sum_{i \in T} (R_i + 1) i=1∑Kzi=S−i∈T∑(Ri+1)
这个方程的非负整数解个数为 (S−∑i∈T(Ri+1)+K−1K−1)\binom{S - \sum_{i \in T} (R_i + 1) + K - 1}{K - 1}(K−1S−∑i∈T(Ri+1)+K−1) ,前提是括号内的值非负。
根据容斥原理,最终的答案公式为:
r=∑T⊆{1,…,K}(−1)∣T∣(S−∑i∈T(Ri+1)+K−1K−1) r= \sum_{T \subseteq \{1, \dots, K\}} (-1)^{|T|} \binom{S - \sum_{i \in T} (R_i + 1) + K - 1}{K - 1} r=T⊆{1,…,K}∑(−1)∣T∣(K−1S−∑i∈T(Ri+1)+K−1)
其中,当组合数的参数 n<kn < kn<k 时,组合数值为 000 。
算法设计
步骤
- 读取输入数据,每组数据包含 K,NK, NK,N 以及 2K2K2K 个整数表示每个部门的 LiL_iLi 和 UiU_iUi 。
- 计算 sumL=∑LisumL = \sum L_isumL=∑Li 和 sumU=∑UisumU = \sum U_isumU=∑Ui 。若 sumL>NsumL > NsumL>N 或 sumU<NsumU < NsumU<N ,输出 000 并继续下一组。
- 计算 S=N−sumLS = N - sumLS=N−sumL 和 Ri=Ui−LiR_i = U_i - L_iRi=Ui−Li 。
- 初始化答案 r=0r = 0r=0 。
- 枚举所有子集 maskmaskmask 从 000 到 2K−12^K - 12K−1 :
- 计算 sumR=∑i∈mask(Ri+1)sumR = \sum_{i \in mask} (R_i + 1)sumR=∑i∈mask(Ri+1) 和子集大小 bits=∣mask∣bits = |mask|bits=∣mask∣ 。
- 计算 T=S−sumRT = S - sumRT=S−sumR ,若 T<0T < 0T<0 则跳过。
- 计算组合数 c=(T+K−1K−1)c = \binom{T + K - 1}{K - 1}c=(K−1T+K−1) 。
- 若 bitsbitsbits 为偶数,则 r=r+cr = r + cr=r+c ;否则 r=r−cr = r - cr=r−c 。
- 输出答案。
组合数计算
由于 K≤7K \le 7K≤7 ,NNN 最大可达 2×1082 \times 10^82×108 ,组合数可能非常大(可达 606060 位),需要使用高精度整数。在 C++\texttt{C++}C++ 中实现高精度类,在 Python\texttt{Python}Python 中直接使用内置的 math.comb\texttt{math.comb}math.comb 。
组合数递推公式为:
(nk)=(nk−1)×n−k+1k \binom{n}{k} = \binom{n}{k-1} \times \frac{n - k + 1}{k} (kn)=(k−1n)×kn−k+1
这样可以避免计算阶乘,每次只需乘以一个整数再除以一个整数。
复杂度分析
- 子集数量:2K≤1282^K \le 1282K≤128 。
- 对于每组数据,计算量很小。
- 高精度运算的位数约为 log10(S+K−1K−1)≤60\log_{10} \binom{S + K - 1}{K - 1} \le 60log10(K−1S+K−1)≤60 位,效率足够。
代码实现
Python 代码
# Cricket Ranking
# UVa ID: 10458
# Verdict: Accepted
# Submission Date: 2026-05-28
# UVa Run Time: 0.020s
#
# 版权所有(C)2026,邱秋。metaphysis # yeah dot net
import sys
import math
def solve() -> None:
data = sys.stdin.read().strip().split()
if not data:
return
idx = 0
results = []
while idx < len(data):
# 读取 K 和 N
K = int(data[idx]); idx += 1
N = int(data[idx]); idx += 1
L = []
U = []
sumL = 0
sumU = 0
for _ in range(K):
l = int(data[idx]); idx += 1
u = int(data[idx]); idx += 1
L.append(l)
U.append(u)
sumL += l
sumU += u
# 无解情况:最小值之和 > N 或 最大值之和 < N
if sumL > N or sumU < N:
results.append("0")
continue
S = N - sumL
# 计算每个部门的可变化范围
R = [U[i] - L[i] for i in range(K)]
# 容斥原理
ans = 0
# 遍历所有子集 (2^K 种)
for mask in range(1 << K):
sumR = 0
bits = 0
for i in range(K):
if mask >> i & 1:
sumR += R[i] + 1
bits += 1
T = S - sumR
if T < 0:
continue
# 计算组合数 C(T + K - 1, K - 1)
n = T + K - 1
k = K - 1
if k == 0:
# 只有一个部门时,组合数为 1(如果 T>=0)
c = 1 if T >= 0 else 0
else:
if n < k:
c = 0
else:
c = math.comb(n, k)
if bits % 2 == 0:
ans += c
else:
ans -= c
results.append(str(ans))
print("\n".join(results))
if __name__ == "__main__":
solve()
C++ 代码
// Cricket Ranking
// UVa ID: 10458
// Verdict: Accepted
// Submission Date: 2026-05-28
// UVa Run Time: 0.250s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int POSITIVE = 1, NEGATIVE = -1, EQUAL = 0;
class BigInteger {
friend ostream& operator<<(ostream&, const BigInteger&);
friend int compare(const BigInteger&, const BigInteger&);
friend bool operator<(const BigInteger&, const BigInteger&);
friend bool operator<=(const BigInteger&, const BigInteger&);
friend bool operator==(const BigInteger&, const BigInteger&);
friend BigInteger operator+(const BigInteger&, const BigInteger&);
friend BigInteger operator-(const BigInteger&, const BigInteger&);
friend BigInteger operator*(const BigInteger&, const BigInteger&);
friend BigInteger operator/(const BigInteger&, const BigInteger&);
friend BigInteger operator%(const BigInteger&, const BigInteger&);
friend BigInteger operator^(const BigInteger&, const unsigned int&);
friend BigInteger operator^(const BigInteger&, const BigInteger&);
friend BigInteger operator<<(const BigInteger&, const unsigned int&);
public:
BigInteger() {};
BigInteger(const long long&);
BigInteger(const string&);
int lastDigit() const {
return digits.front();
}
~BigInteger() {};
private:
void zeroJustify(void);
// 采用10000作为基数。数位宽度为4。
static const int base = 10000;
static const int width = 4;
int sign;
vector < int > digits;
};
BigInteger operator+(const BigInteger&, const BigInteger&);
BigInteger operator-(const BigInteger&, const BigInteger&);
// 将长整型数转换为大整数。
BigInteger::BigInteger(const long long& value) {
if (value == 0) {
sign = POSITIVE;
digits.push_back(0);
} else {
// 不断模基数取余得到各个数位。
sign = (value >= 0 ? POSITIVE : NEGATIVE);
long long number = abs(value);
while (number) {
digits.push_back(number % base);
number /= base;
}
}
// 移除前导零。
zeroJustify();
};
// 将十进制的字符串转换为大整数。
BigInteger::BigInteger(const string& value) {
if (value.length() == 0) {
sign = POSITIVE;
digits.push_back(0);
} else {
sign = value[0] == '-' ? NEGATIVE : POSITIVE;
// 四个数字作为一组,转换为整数存储到数位数组中。
string block;
for (int index = value.length() - 1; index >= 0; index--) {
if (isdigit(value[index])) block.insert(block.begin(), value[index]);
if (block.length() == width) {
digits.push_back(stoi(block));
block.clear();
}
}
if (block.length() > 0) digits.push_back(stoi(block));
}
// 移除前导零。
zeroJustify();
}
// 重载输出符号以输出大整数。
ostream& operator<<(ostream& os, const BigInteger& number) {
os << (number.sign > 0 ? "" : "-");
os << number.digits[number.digits.size() - 1];
for (int i = (int)number.digits.size() - 2; i >= 0; i--)
os << setw(number.width) << setfill('0') << number.digits[i];
return os;
}
// 移除无效的前导零。
void BigInteger::zeroJustify(void) {
for (int i = digits.size() - 1; i >= 1; i--) {
if (digits[i] == 0) digits.erase(digits.begin() + i);
else break;
}
if (digits.size() == 1 && digits[0] == 0) sign = POSITIVE;
}
// 比较两个高精度整数的大小。
// x大于y,返回1,x小于y,返回-1,x等于y,返回0。
// 为了除法的需要,对未经前导零调整的整数也能正确处理。
int compare(const BigInteger& x, const BigInteger& y) {
// 符号不同,正数大于负数。
if (x.sign == POSITIVE && y.sign == NEGATIVE || x.sign == NEGATIVE && y.sign == POSITIVE)
return (x.sign == POSITIVE ? 1 : -1);
// 确定x和y的有效数位,即前导零不计入有效数位。
int xDigitNumber = x.digits.size() - 1;
for (; xDigitNumber && x.digits[xDigitNumber] == 0; xDigitNumber--) ;
int yDigitNumber = y.digits.size() - 1;
for (; yDigitNumber && y.digits[yDigitNumber] == 0; yDigitNumber--) ;
// 符号相同,同为正数,数位越多越大,同为负数,数位越多越小。
if (xDigitNumber > yDigitNumber) return (x.sign == POSITIVE ? 1 : -1);
// 符号相同,同为正数,数位越少越小,同为负数,数位越少越大。
if (xDigitNumber < yDigitNumber) return (x.sign == NEGATIVE ? 1 : -1);
// 符号相同,数位相同,逐位比较。
for (int index = xDigitNumber; index >= 0; index--) {
if (x.digits[index] > y.digits[index]) return (x.sign == POSITIVE ? 1 : -1);
if (x.digits[index] < y.digits[index]) return (x.sign == NEGATIVE ? 1 : -1);
}
// 两数相等。
return 0;
}
// 等于比较运算符。
bool operator==(const BigInteger& x, const BigInteger& y) {
return compare(x, y) == 0;
}
// 小于比较运算符。
bool operator<(const BigInteger& x, const BigInteger& y) {
return compare(x, y) < 0;
}
// 小于等于比较运算符。
bool operator<=(const BigInteger& x, const BigInteger& y) {
return compare(x, y) <= 0;
}
// 高精度整数加法。
BigInteger operator+(const BigInteger& x, const BigInteger& y) {
BigInteger z;
// 如果两个加数的符号不同,转换为减法运算。
if (x.sign == NEGATIVE && y.sign == POSITIVE) {
z = x;
z.sign = POSITIVE;
return (y - z);
} else if (x.sign == POSITIVE && y.sign == NEGATIVE) {
z = y;
z.sign = POSITIVE;
return (x - z);
}
// 确保x的位数比y的位数多,便于计算。
if (x.digits.size() < y.digits.size()) return (y + x);
// 两个加数的符号相同时才进行加法运算。预先为结果分配存储空间。
z.sign = x.sign + y.sign >= 0 ? POSITIVE : NEGATIVE;
z.digits.resize(max(x.digits.size(), y.digits.size()) + 1);
fill(z.digits.begin(), z.digits.end(), 0);
// 逐位相加,考虑进位。
int index = 0, carry = 0;
for (; index < x.digits.size(); index++) {
// 获取对应位的和。
int sum = x.digits[index] + carry;
sum += index < y.digits.size() ? y.digits[index] : 0;
// 确定进位。
carry = sum / z.base;
// 将和保存到结果的相应位中。
z.digits[index] = sum % z.base;
}
// 保存最后可能产生的进位。
z.digits[index] = carry;
// 移除前导零。
z.zeroJustify();
return z;
}
// 高精度整数减法。
BigInteger operator-(const BigInteger& x, const BigInteger& y) {
BigInteger z;
// 当x和y至少有一个是负数,转换为加法运算。
if (x.sign == NEGATIVE || y.sign == NEGATIVE) {
z = y;
z.sign = -y.sign;
return x + z;
}
// 都为正数,确保x大于y,便于计算。
if (x < y) {
z = y - x;
z.sign = NEGATIVE;
return z;
}
// 设置符号位并预先分配存储空间。
z.sign = POSITIVE;
z.digits.resize(max(x.digits.size(), y.digits.size()));
fill(z.digits.begin(), z.digits.end(), 0);
// 逐位相减,考虑借位。
int index = 0, borrow = 0;
for (; index < x.digits.size(); index++) {
// 获取对应位的差。
int difference = x.digits[index] - borrow;
difference -= index < y.digits.size() ? y.digits[index] : 0;
// 确定是否有借位。
borrow = 0;
if (difference < 0) {
difference += z.base;
borrow = 1;
}
// 保存相应位差的结果。
z.digits[index] = difference % z.base;
}
// 移除前导零。
z.zeroJustify();
return z;
}
// 高精度整数乘法。
BigInteger operator*(const BigInteger& x, const BigInteger& y) {
BigInteger z;
// 设置符号位并预先分配存储空间。
z.sign = x.sign * y.sign;
z.digits.resize(x.digits.size() + y.digits.size());
fill(z.digits.begin(), z.digits.end(), 0);
// 一行一行相乘然后相加。
for (int i = 0; i < y.digits.size(); i++)
for (int j = 0; j < x.digits.size(); j++) {
z.digits[i + j] += x.digits[j] * y.digits[i];
z.digits[i + j + 1] += z.digits[i + j] / z.base;
z.digits[i + j] %= z.base;
}
// 移除前导零。
z.zeroJustify();
return z;
}
// 高精度整数除法,为整除运算。
BigInteger operator/(const BigInteger& x, const BigInteger& y) {
// z表示整除得到的商,r表示每次试除时的被除数。
BigInteger z, r;
// 设置商和被除数的符号位。
z.sign = x.sign * y.sign;
r.sign = POSITIVE;
// 为商z和表示被除数的r预先分配存储空间。
z.digits.resize(x.digits.size() - y.digits.size() + 1);
r.digits.resize(y.digits.size() + 1);
// 初始化值。
fill(z.digits.begin(), z.digits.end(), 0);
fill(r.digits.begin(), r.digits.end(), 0);
// 从高位到低位逐位试除得到对应位的商。
for (int i = x.digits.size() - 1; i >= 0; i--) {
// 获取被除数,将上一次未被除尽的余数的移到高位加上当前数位继续除。
r.digits.insert(r.digits.begin(), x.digits[i]);
// 通过二分试除法得到对应位的商。
int low = 0, high = z.base - 1, middle = (high + low + 1) >> 1;
while (low < high) {
if ((y * BigInteger(middle)) <= r) low = middle;
else high = middle - 1;
middle = (high + low + 1) >> 1;
}
// 执行减法,从被除数中减去指定数量的y。
for (int index = 0; index < y.digits.size(); index++) {
int difference = r.digits[index] - middle * y.digits[index];
// 确定是否有借位产生。
int borrow = 0;
if (difference < 0) borrow = (z.base - 1 - difference) / z.base;
// 高位减去借位数量。
r.digits[index + 1] -= borrow;
// 低位加上借位。
difference += z.base * borrow;
r.digits[index] = difference % z.base;
}
// 将对应位的商存入结果中。
z.digits.insert(z.digits.begin(), middle);
}
// 移除前导零。
z.zeroJustify();
return z;
}
// 计算组合数 C(n, k)
BigInteger C(int n, int k) {
if (k < 0 || k > n) return BigInteger(0);
if (k > n - k) k = n - k;
BigInteger r = 1;
for (int i = 1; i <= k; ++i) {
r = r * (n - k + i);
r = r / i;
}
return r;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0);
int K;
long long N;
while (cin >> K >> N) {
vector<int> L(K), U(K);
long long sumL = 0, sumU = 0;
for (int i = 0; i < K; ++i) {
cin >> L[i] >> U[i];
sumL += L[i];
sumU += U[i];
}
// 无解判断
if (sumL > N || sumU < N) {
cout << "0\n";
continue;
}
long long S = N - sumL;
vector<int> R(K);
for (int i = 0; i < K; ++i) R[i] = U[i] - L[i];
// 容斥原理
BigInteger r = 0;
for (int mask = 0; mask < (1 << K); ++mask) {
long long sumR = 0;
int bits = 0;
for (int i = 0; i < K; ++i) if (mask >> i & 1) {
sumR += R[i] + 1;
++bits;
}
long long T = S - sumR;
if (T < 0) continue;
// 计算组合数 C(T + K - 1, K - 1)
int n = T + K - 1, k = K - 1;
BigInteger c;
if (k == 0) c = 1;
else if (n < k) c = 0;
else c = C(n, k);
if (bits % 2 == 0) r = r + c;
else r = r - c;
}
cout << r << '\n';
}
return 0;
}
1386

被折叠的 条评论
为什么被折叠?



