向再见说再见
题意:
有一场比赛,有两支队伍,每支队伍有n(<=500)个人,每个人的能力值不同,一支队伍的一个人和另外一支队伍的人PK,每个人只能战斗一次,能力值大的赢并且加一分,给出一个数K,问两支队伍分数相差为K的方案数。
题解:
方法一:
首先这是一道排列计数的问题,首先将所有人的能力值全部从小到大排一次序,这样是为了决策的单调性,只能从前面转移到后面。
很容易定义状态dp[i][a][b]表示前i个人的对决中,第一支队伍得了a分,第二支队伍得了b分的方案数。
现在考虑转移一个人要么对该支队伍贡献一分,要么就让后面的人击败他。
① dp[i][a][b] += dp[i-1][a][b] 表示让后面的人击败他
② dp[i][a][b] += dp[i-1][a-1][b] * (cnt2 - (a - 1) - b) 表示目前的i个人是第一支队伍的击败他之前没有战斗过的第二支队伍的人
代码:
#include <bits/stdc++.h> using namespace std; #define LL long long const int N = 6e2 + 7; const int mod = 1e9 + 7; int n, S[N], B[N], K, cnt1, cnt2; typedef pair <int , int> pii; pii xi[N]; LL dp[N][N/2][N/2], ans; int main () { scanf ("%d%d", &n, &K); for (int i = 1; i <= 2 * n; ++ i) { scanf ("%d", &xi[i].first); if (i <= n) xi[i].second = 1; else xi[i].second = 2; } sort (xi + 1, xi + 1 + 2 * n); dp[0][0][0] = 1; for (int i = 1; i <= 2 * n; ++ i) { cnt1 += xi[i].second == 1; cnt2 += xi[i].second == 2; for (int a = 0; a <= min (cnt1, cnt2); ++ a) for (int b = 0; a + b <= min (cnt1, cnt2); ++ b) { if (xi[i].second == 1) { dp[i][a][b] = (dp[i][a][b] + dp[i - 1][a][b]) % mod; if (a) dp[i][a][b] = (dp[i][a][b] + (LL)dp[i - 1][a - 1][b] * (cnt2 - a - b + 1) % mod) % mod; } if (xi[i].second == 2) { dp[i][a][b] = (dp[i][a][b] + dp[i - 1][a][b]) % mod; if (b) dp[i][a][b] = (dp[i][a][b] + (LL)dp[i - 1][a][b - 1] * (cnt1 - a - b + 1) % mod) % mod; } } } if ((n ^ K) & 1) cout << "0" << endl; else if (K == 0) cout << dp[2 * n][n / 2][n / 2] << endl; else cout << (dp[2 * n][(n + K) / 2][(n - K) / 2] + dp[2 * n][(n - K) / 2][(n + K) / 2]) % mod << endl; return 0; }
方法二:
这是一个比较精妙的dp吧~
定义状态dp[i][j]表示第一支队伍前i个人中得j分的方案数。
那么考虑转移
dp[i][j] = dp[i-1][j] (继承前一个状态的方案)
dp[i][j] = dp[i-1][j-1] * (num[i] - j + 1) (针对于第i可以赢的方案)
令h[i] = dp[n][i] * (n - i)! 表示前n个人至少的i分的方案,g[i]表示n个人刚好得i分的方案
g[i] = h[i] - (g[i+1] * C(i+1, i), + g[i+2] * C(i+2,i) + g[n] * C(n,i))
代码:
#include <bits/stdc++.h> using namespace std; #define LL long long const int N = 2e3 + 7; const int mod = 1e9 + 7; int ai[N], bi[N], n, K, num[N]; LL fac[N], C[N][N], f[N][N], g[N]; int main () { scanf ("%d%d", &n, &K); for (int i = 1; i <= n; ++ i) scanf ("%d", &ai[i]); for (int i = 1; i <= n; ++ i) scanf ("%d", &bi[i]); sort (ai + 1, ai + 1 + n); sort (bi + 1, bi + 1 + n); fac[0] = 1; for (int i = 1; i < N; ++ i) fac[i] = fac[i - 1] * i % mod; for (int i = 0; i < N; ++ i) C[i][0] = 1; for (int i = 1; i < N; ++ i) for (int j = 1; j < N; ++j) C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod; for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n; ++ j) if (ai[i] > bi[j]) ++ num[i]; f[0][0] = 1; for (int i = 1; i <= n; ++ i) for (int j = 0; j <= i; ++ j) { f[i][j] = f[i - 1][j]; if (j) f[i][j] = (f[i][j] + f[i - 1][j - 1] * (num[i] - j + 1) % mod) % mod; } for (int i = n; i >= 1; -- i) { g[i] = f[n][i] * fac[n - i] % mod; for (int j = i + 1; j <= n; ++j) g[i] = (g[i] - g[j] * C[j][i] % mod + mod) % mod; } if ((K ^ n) & 1) cout << "0" << endl; else if (K == 0) cout << g[n / 2] << endl; else cout << (g[(n + K) / 2] + g[(n - K) / 2]) % mod << endl; return 0; }
总结:
需要先搞好dp的转移的顺序,还有的就是要认真模拟调试一下,看数据的变化是否在计算范围之内。