题解:洛谷 P2098 Team Building
【题目来源】
洛谷:P2098 [USACO16DEC] Team Building P - 洛谷
【题目描述】
每年,Farmer John 都会带着他的 \(N\) 头奶牛参加州展览会的“最佳展示”比赛。他的劲敌 Farmer Paul 也会带着他的 \(M\) 头奶牛参加比赛(\(1 \leq N \leq 1000, 1 \leq M \leq 1000\))。
参加比赛的 \(N + M\) 头奶牛每头都会获得一个单独的整数得分。然而,今年的最终比赛将由 \(K\) 头奶牛组成的团队决定(\(1 \leq K \leq 10\)),规则如下:
Farmer John 和 Farmer Paul 各自选择 \(K\) 头奶牛组成团队进行比赛。这两个团队的奶牛将按得分高低配对:
FJ 团队中得分最高的奶牛与 FP 团队中得分最高的奶牛配对,FJ 团队中得分第二高的奶牛与 FP 团队中得分第二高的奶牛配对,依此类推。如果在每一对中,FJ 的奶牛得分都更高,那么 FJ 获胜。
请帮助 FJ 计算他和 FP 可以选择团队的不同方式的数量,使得 FJ 能够赢得比赛。也就是说,每个不同的(FJ 的 \(K\) 头奶牛集合,FP 的 \(K\) 头奶牛集合)对,只要 FJ 获胜,都应被计入。输出结果对 \(1\,000\,000\,009\) 取模。
【输入】
输入的第一行包含 \(N\)、\(M\) 和 \(K\)。\(K\) 的值不会超过 \(N\) 或 \(M\)。
第二行包含 FJ 的 \(N\) 头奶牛的得分。
第三行包含 FP 的 \(M\) 头奶牛的得分。
【输出】
输出 FJ 和 FP 可以选择团队的方式数量,使得 FJ 获胜,结果对 \(1\,000\,000\,009\) 取模。
【输入样例】
10 10 3
1 2 2 6 6 7 8 9 14 17
1 3 8 10 10 16 16 18 19 19
【输出样例】
382
【核心思想】
-
问题分析:FJ 有 \(N\) 头奶牛,FP 有 \(M\) 头奶牛,各自选择 \(K\) 头组成团队。两团队的奶牛按得分从高到低一一配对,若每一对中 FJ 的奶牛得分都严格大于 FP 的奶牛得分,则 FJ 获胜。求 FJ 和 FP 选择团队的不同方式数量(对 \(10^9 + 9\) 取模)。这是一个三维线性DP + 容斥原理问题。
-
算法选择:
- 降序排序(Descending Sort):将 FJ 和 FP 的得分分别按降序排序,使得 \(a[i]\) 和 \(b[j]\) 分别是当前考虑范围内的最小元素
- 三维DP(3D Dynamic Programming):\(dp[i][j][k]\) 表示从 FJ 的前 \(i\) 头奶牛和 FP 的前 \(j\) 头奶牛中,选出 \(k\) 对且 FJ 每对都获胜的方案数
- 容斥原理(Inclusion-Exclusion Principle):在状态转移时,用容斥原理处理"不选 FJ 的第 \(i\) 头"和"不选 FP 的第 \(j\) 头"两种情况的重复计数问题
-
关键步骤:
- 初始化:
- 读入 \(N\)、\(M\)、\(K\),以及 FJ 的得分数组 \(a[1..N]\) 和 FP 的得分数组 \(b[1..M]\)
sort(a + 1, a + n + 1, greater<int>()):FJ 得分降序排序sort(b + 1, b + m + 1, greater<int>()):FP 得分降序排序- 边界条件:\(dp[i][j][0] = 1\)(对于所有 \(0 \leq i \leq N\)、\(0 \leq j \leq M\),选 \(0\) 对的方案数为 \(1\))
- 状态转移(枚举团队大小 \(k\) 从 \(1\) 到 \(K\)):
- 枚举 \(i\) 从 \(k\) 到 \(N\)(FJ 至少需要 \(k\) 头)
- 枚举 \(j\) 从 \(k\) 到 \(M\)(FP 至少需要 \(k\) 头)
- 容斥转移(先计算不考虑 \(a[i]\) 和 \(b[j]\) 的方案数):
- \(dp[i][j][k] = dp[i-1][j][k]\)(不选 FJ 的第 \(i\) 头)
- \(dp[i][j][k] += dp[i][j-1][k]\)(不选 FP 的第 \(j\) 头)
- \(dp[i][j][k] -= dp[i-1][j-1][k]\)(容斥去重,两者都不选的情况被重复计算)
- 注意取模:\((x + mod) \% mod\) 防止负数
- 配对转移(若 \(a[i] > b[j]\),可将它们作为第 \(k\) 对):
- \(dp[i][j][k] += dp[i-1][j-1][k-1]\)(从前 \(i-1\) 和 \(j-1\) 中选 \(k-1\) 对,再加上当前这对)
- 输出答案:\(dp[N][M][K]\)(从全部 \(N\) 头和 \(M\) 头中选 \(K\) 对的方案数)
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(N \times M \times K)\),三维枚举,\(K \leq 10\),\(N, M \leq 1000\)
- 空间复杂度:\(O(N \times M \times K)\),三维DP数组,可通过滚动数组优化至 \(O(N \times M)\)
-
三维DP + 容斥原理的核心思想:
- 降序排序的妙处:排序后 \(a[i]\) 和 \(b[j]\) 是当前考虑范围内的最小值。若 \(a[i] > b[j]\),则对于任意 \(p \leq i\)、\(q \leq j\) 都有 \(a[p] \geq a[i] > b[j] \geq b[q]\),保证配对的单调性
- 容斥去重技巧:在计算"从 \([1..i]\) 和 \([1..j]\) 中选 \(k\) 对"时,直接考虑"不选 \(a[i]\)"和"不选 \(b[j]\)"会重复计算"两者都不选"的情况,因此需要减去 \(dp[i-1][j-1][k]\)
- 配对条件的处理:只有在 \(a[i] > b[j]\) 时才允许将 \(a[i]\) 和 \(b[j]\) 配成一对,保证 FJ 获胜。由于降序排列,当前这对的 FJ 奶牛是剩余中最弱的,若能赢过 FP 最弱的,则前面所有配对也一定满足条件
- 三维状态设计:\(dp[i][j][k]\) 同时记录 FJ 前 \(i\) 头、FP 前 \(j\) 头、已选 \(k\) 对三个维度,保证状态无后效性
- 适用于组合计数、匹配方案数、带约束的选对方案类问题
【算法标签】
普及+ #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long // 将 int 定义为 long long,防止模运算溢出
const int N = 1005, mod = 1e9 + 9; // N: 最大奶牛数量, mod: 模数
int n, m, K; // n: FJ的奶牛数, m: FP的奶牛数, K: 团队大小
int a[N], b[N]; // a[i]: FJ第i头奶牛的得分, b[i]: FP第i头奶牛的得分
// dp[i][j][k]: 从FJ的前i头奶牛和FP的前j头奶牛中,选出k对使得FJ每对都获胜的方案数
// 注意:a和b已按降序排序,所以a[i]是FJ前i头中最小的,b[j]是FP前j头中最小的
int dp[N][N][15];
signed main() // 使用 signed 替代 int,因为 #define int long long
{
cin >> n >> m >> K; // 读入FJ奶牛数、FP奶牛数、团队大小
// 读入FJ的N头奶牛得分
for (int i = 1; i <= n; i++)
cin >> a[i];
// 读入FP的M头奶牛得分
for (int i = 1; i <= m; i++)
cin >> b[i];
// 将FJ和FP的得分按降序排序
// 排序后,a[i] >= a[i+1],b[i] >= b[i+1]
// 这样dp[i][j]中,a[i]和b[j]分别是当前考虑的最小元素
sort(a + 1, a + n + 1, greater<int>());
sort(b + 1, b + m + 1, greater<int>());
// 初始化:选0对时,方案数为1(什么都不选也是一种方案)
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++)
dp[i][j][0] = 1;
// 动态规划:枚举团队大小k,FJ前i头,FP前j头
for (int k = 1; k <= K; k++)
{
for (int i = k; i <= n; i++) // i至少为k(FJ至少需要k头奶牛)
{
for (int j = k; j <= m; j++) // j至少为k(FP至少需要k头奶牛)
{
// 容斥原理:计算不考虑a[i]和b[j]时的方案数
// dp[i-1][j][k]: 不选FJ的第i头奶牛
// dp[i][j-1][k]: 不选FP的第j头奶牛
// 两者相加后,dp[i-1][j-1][k]被重复计算了,需要减去
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k]) % mod;
dp[i][j][k] = (dp[i][j][k] + dp[i][j - 1][k]) % mod;
dp[i][j][k] = (dp[i][j][k] - dp[i - 1][j - 1][k] + mod) % mod;
// 如果FJ的第i头奶牛得分 > FP的第j头奶牛得分
// 则可以将它们配成一对(作为第k对,且FJ获胜)
// 加上从前面i-1和j-1中选k-1对的方案数
if (a[i] > b[j])
{
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k - 1]) % mod;
}
}
}
}
// 输出答案:从FJ的n头和FP的m头中选K对,FJ获胜的方案数
cout << dp[n][m][K] << endl;
return 0;
}
【运行结果】
10 10 3
1 2 2 6 6 7 8 9 14 17
1 3 8 10 10 16 16 18 19 19
382
浙公网安备 33010602011771号