Hard
题目描述
给你三个整数 m、n 和 k。
有一个大小为 m × n 的矩形网格,包含 k 个相同的棋子。返回所有有效排列中每对棋子之间曼哈顿距离的总和。
有效排列是指将所有 k 个棋子放置在网格上,每个格子最多放一个棋子。
由于答案可能很大,请返回对 10^9 + 7 取模的结果。
两个格子 (xi, yi) 和 (xj, yj) 之间的曼哈顿距离是 |xi - xj| + |yi - yj|。
示例 1:
输入:m = 2, n = 2, k = 2
输出:8
解释:
棋盘上棋子的有效排列有:
- 在前 4 种排列中,两个棋子之间的曼哈顿距离是 1。
- 在最后 2 种排列中,两个棋子之间的曼哈顿距离是 2。
因此,所有有效排列的曼哈顿距离总和是 1 + 1 + 1 + 1 + 2 + 2 = 8。
示例 2:
输入:m = 1, n = 4, k = 3
输出:20
解释:
棋盘上棋子的有效排列有:
- 第一种和最后一种排列的曼哈顿距离总和是 1 + 1 + 2 = 4。
- 中间两种排列的曼哈顿距离总和是 1 + 2 + 3 = 6。
所有排列中所有棋子对之间的曼哈顿距离总和是 4 + 6 + 6 + 4 = 20。
约束条件:
- 1 <= m, n <= 10^5
- 2 <= m * n <= 10^5
- 2 <= k <= m * n
解题思路
这是一道需要数学思维的组合数学题目。关键洞察是:我们不需要枚举所有排列,而是考虑每一对位置在所有排列中被选中的次数。
核心思路:
固定两个位置的思考:对于网格中任意两个位置 (i₁, j₁) 和 (i₂, j₂),我们需要计算这两个位置同时被棋子占据的排列数量。
组合数计算:如果我们固定了两个特定位置放置棋子,那么剩下的 k-2 个棋子需要从剩余的 m×n-2 个位置中选择,方案数为 C(m×n-2, k-2)。
距离贡献分离:曼哈顿距离可以分解为 x 坐标差的绝对值和 y 坐标差的绝对值之和。我们可以分别计算这两部分的贡献。
优化计算:
- 对于 x 坐标:计算所有行对 (i₁, i₂) 的 |i₁ - i₂| × n² × C(m×n-2, k-2) 的总和
- 对于 y 坐标:计算所有列对 (j₁, j₂) 的 |j₁ - j₂| × m² × C(m×n-2, k-2) 的总和
数学公式推导:
- x 方向贡献:∑∑|i-j| × n² = n² × m(m-1)(m+1)/6
- y 方向贡献:∑∑|i-j| × m² = m² × n(n-1)(n+1)/6
最终答案 = C(m×n-2, k-2) × [n² × m(m-1)(m+1)/6 + m² × n(n-1)(n+1)/6]
代码实现
class Solution {
public:
static const int MOD = 1000000007;
long long modPow(long long base, long long exp, long long mod) {
long long result = 1;
while (exp > 0) {
if (exp % 2 == 1) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exp /= 2;
}
return result;
}
long long modInverse(long long a, long long mod) {
return modPow(a, mod - 2, mod);
}
long long combination(int n, int r) {
if (r > n || r < 0) return 0;
if (r == 0 || r == n) return 1;
long long num = 1, den = 1;
for (int i = 0; i < r; i++) {
num = (num * (n - i)) % MOD;
den = (den * (i + 1)) % MOD;
}
return (num * modInverse(den, MOD)) % MOD;
}
int distanceSum(int m, int n, int k) {
long long total = (long long)m * n;
long long comb = combination(total - 2, k - 2);
// x direction contribution: sum of |i1 - i2| over all pairs
long long xContrib = ((long long)m * (m - 1) * (m + 1) / 6) % MOD;
xContrib = (xContrib * n % MOD * n) % MOD;
// y direction contribution: sum of |j1 - j2| over all pairs
long long yContrib = ((long long)n * (n - 1) * (n + 1) / 6) % MOD;
yContrib = (yContrib * m % MOD * m) % MOD;
long long result = (xContrib + yContrib) % MOD;
result = (result * comb) % MOD;
return (int)result;
}
};
class Solution:
def distanceSum(self, m: int, n: int, k: int) -> int:
MOD = 1000000007
def mod_inverse(a, mod):
return pow(a, mod - 2, mod)
def combination(n, r):
if r > n or r < 0:
return 0
if r == 0 or r == n:
return 1
num = 1
den = 1
for i in range(r):
num = (num * (n - i)) % MOD
den = (den * (i + 1)) % MOD
return (num * mod_inverse(den, MOD)) % MOD
total = m * n
comb = combination(total - 2, k - 2)
# x direction contribution
x_contrib = (m * (m - 1) * (m + 1) // 6) % MOD
x_contrib = (x_contrib * n * n) % MOD
# y direction contribution
y_contrib = (n * (n - 1) * (n + 1) // 6) % MOD
y_contrib = (y_contrib * m * m) % MOD
result = (x_contrib + y_contrib) % MOD
result = (result * comb) % MOD
return result
public class Solution {
private const int MOD = 1000000007;
private long ModPow(long baseNum, long exp, long mod) {
long result = 1;
while (exp > 0) {
if (exp % 2 == 1) {
result = (result * baseNum) % mod;
}
baseNum = (baseNum * baseNum) % mod;
exp /= 2;
}
return result;
}
private long ModInverse(long a, long mod) {
return ModPow(a, mod - 2, mod);
}
private long Combination(int n, int r) {
if (r > n || r < 0) return 0;
if (r == 0 || r == n) return 1;
long num = 1, den = 1;
for (int i = 0; i < r; i++) {
num = (num * (n - i)) % MOD;
den = (den * (i + 1)) % MOD;
}
return (num * ModInverse(den, MOD)) % MOD;
}
public int DistanceSum(int m, int n, int k) {
long total = (long)m * n;
long comb = Combination((int)total - 2, k - 2);
// x direction contribution
long xContrib = ((long)m * (m - 1) * (m + 1) / 6) % MOD;
xContrib = (xContrib * n % MOD * n) % MOD;
// y direction contribution
long yContrib = ((long)n * (n - 1) * (n + 1) / 6) % MOD;
yContrib = (yContrib * m % MOD * m) % MOD;
long result = (xContrib + yContrib) % MOD;
result = (result * comb) % MOD;
return (int)result;
}
}
var distanceSum = function(m, n, k) {
const MOD = 1000000007;
// Total number of ways to choose k positions from m*n positions
const totalArrangements = comb(m * n, k, MOD);
// For each pair of positions, count how many arrangements contain both
const arrangementsPerPair = comb(m * n - 2, k - 2, MOD);
let totalDistance = 0;
// Sum Manhattan distances for all pairs of positions
for (let i1 = 0; i1 < m; i1++) {
for (let j1 = 0; j1 < n; j1++) {
for (let i2 = 0; i2 < m; i2++) {
for (let j2 = 0; j2 < n; j2++) {
if (i1 === i2 && j1 === j2) continue;
const manhattanDist = Math.abs(i1 - i2) + Math.abs(j1 - j2);
totalDistance = (totalDistance + (manhattanDist * arrangementsPerPair) % MOD) % MOD;
}
}
}
}
return totalDistance;
};
function comb(n, k, mod) {
if (k > n || k < 0) return 0;
if (k === 0 || k === n) return 1;
// Calculate n! / (k! * (n-k)!)
let num = 1, den = 1;
for (let i = 0; i < k; i++) {
num = (num * ((n - i) % mod)) % mod;
den = (den * (i + 1)) % mod;
}
return (num * modInverse(den, mod)) % mod;
}
function modInverse(a, mod) {
return modPow(a, mod - 2, mod);
}
function modPow(base, exp, mod) {
let result = 1;
base %= mod;
while (exp > 0) {
if (exp & 1) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exp >>= 1;
}
return result;
}
复杂度分析
| 维度 | 复杂度 |
|---|---|
| 时间复杂度 | O(k) |
| 空间复杂度 | O(1) |
说明:
- 时间复杂度:主要消耗在计算组合数 C(m×n-2, k-2),需要 O(k) 时间
- 空间复杂度:只使用了常数级别的额外空间