向再见说再见

题意:

有一场比赛,有两支队伍,每支队伍有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的转移的顺序,还有的就是要认真模拟调试一下,看数据的变化是否在计算范围之内。

 

原文地址:https://www.cnblogs.com/xgtao/p/6021062.html