UVa 10458 Cricket Ranking

题目描述

200320032003 年板球世界杯正在南非举行,组委会希望根据球员在世界杯中的表现制定一个新的排名系统。排名系统包含 KKK 个不同的部门(例如击球、防守等),每个部门 iii 的最高得分有一个范围 [Li,Ui][L_i, U_i][Li,Ui],且所有部门的最高得分之和必须恰好等于 NNN 。需要计算有多少种不同的分配方案。

问题分析

问题本质

这是一个有上下界的整数划分问题。设第 iii 个部门的最高得分为 xix_ixi ,则:

  • Li≤xi≤UiL_i \le x_i \le U_iLixiUi
  • ∑i=1Kxi=N\sum_{i=1}^{K} x_i = Ni=1Kxi=N

求满足条件的整数解 (x1,x2,…,xK)(x_1, x_2, \dots, x_K)(x1,x2,,xK) 的个数。

变量代换

yi=xi−Liy_i = x_i - L_iyi=xiLi ,则 0≤yi≤Ui−Li0 \le y_i \le U_i - L_i0yiUiLi 。设 Ri=Ui−LiR_i = U_i - L_iRi=UiLi ,则约束变为:

  • 0≤yi≤Ri0 \le y_i \le R_i0yiRi
  • ∑i=1Kyi=S\sum_{i=1}^{K} y_i = Si=1Kyi=S ,其中 S=N−∑i=1KLiS = N - \sum_{i=1}^{K} L_iS=Ni=1KLi

无解判断

S<0S < 0S<0∑i=1KUi<N\sum_{i=1}^{K} U_i < Ni=1KUi<N ,则无解,输出 000

容斥原理

这是一个有上界的整数解个数问题,可以用容斥原理求解。

首先,不考虑上界 yi≤Riy_i \le R_iyiRi ,只考虑 yi≥0y_i \ge 0yi0∑yi=S\sum y_i = Syi=S 的非负整数解个数为:

(S+K−1K−1) \binom{S + K - 1}{K - 1} (K1S+K1)

这是因为在 KKK 个变量中分配 SSS 个不可区分的球,相当于在 S+K−1S + K - 1S+K1 个位置中选择 K−1K - 1K1 个隔板。

接下来,考虑至少有一个变量超过上界的情况。对于子集 T⊆{1,2,…,K}T \subseteq \{1, 2, \dots, K\}T{1,2,,K} ,令 TTT 中的变量都满足 yi≥Ri+1y_i \ge R_i + 1yiRi+1 。令 zi=yi−(Ri+1)z_i = y_i - (R_i + 1)zi=yi(Ri+1) ,则 zi≥0z_i \ge 0zi0 ,且:

∑i=1Kzi=S−∑i∈T(Ri+1) \sum_{i=1}^{K} z_i = S - \sum_{i \in T} (R_i + 1) i=1Kzi=SiT(Ri+1)

这个方程的非负整数解个数为 (S−∑i∈T(Ri+1)+K−1K−1)\binom{S - \sum_{i \in T} (R_i + 1) + K - 1}{K - 1}(K1SiT(Ri+1)+K1) ,前提是括号内的值非负。

根据容斥原理,最终的答案公式为:

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(K1SiT(Ri+1)+K1)

其中,当组合数的参数 n<kn < kn<k 时,组合数值为 000

算法设计

步骤

  1. 读取输入数据,每组数据包含 K,NK, NK,N 以及 2K2K2K 个整数表示每个部门的 LiL_iLiUiU_iUi
  2. 计算 sumL=∑LisumL = \sum L_isumL=LisumU=∑UisumU = \sum U_isumU=Ui 。若 sumL>NsumL > NsumL>NsumU<NsumU < NsumU<N ,输出 000 并继续下一组。
  3. 计算 S=N−sumLS = N - sumLS=NsumLRi=Ui−LiR_i = U_i - L_iRi=UiLi
  4. 初始化答案 r=0r = 0r=0
  5. 枚举所有子集 maskmaskmask0002K−12^K - 12K1
    • 计算 sumR=∑i∈mask(Ri+1)sumR = \sum_{i \in mask} (R_i + 1)sumR=imask(Ri+1) 和子集大小 bits=∣mask∣bits = |mask|bits=mask
    • 计算 T=S−sumRT = S - sumRT=SsumR ,若 T<0T < 0T<0 则跳过。
    • 计算组合数 c=(T+K−1K−1)c = \binom{T + K - 1}{K - 1}c=(K1T+K1)
    • bitsbitsbits 为偶数,则 r=r+cr = r + cr=r+c ;否则 r=r−cr = r - cr=rc
  6. 输出答案。

组合数计算

由于 K≤7K \le 7K7NNN 最大可达 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)=(k1n)×knk+1

这样可以避免计算阶乘,每次只需乘以一个整数再除以一个整数。

复杂度分析

  • 子集数量:2K≤1282^K \le 1282K128
  • 对于每组数据,计算量很小。
  • 高精度运算的位数约为 log⁡10(S+K−1K−1)≤60\log_{10} \binom{S + K - 1}{K - 1} \le 60log10(K1S+K1)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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值