【2022 省选训练赛 Contest 06 B】stat(DP)

stat

题目链接:2022 省选训练赛 Contest 06 B

题目大意

问你有多少对长度为 n 的排列的分数大于等于 k。
两个排列的分数是它们每一位取最大值的和。

思路

考虑固定一个排列按顺序,然后每个跟另外一个匹配,然后答案乘上排列的种数。

然后因为是最大值考虑从大往小 DP:
可以考虑每个数要贡献多少次(\(2,1,0\) 三种可能)
那如果你不当最大值,前面肯定要一个比它大的挡住,所以我们可以这样 DP:
\(f_{i,j,k}\) 为当前到位置 \(i\),前面能拿来挡的有 \(j\) 个,然后当前的分数是 \(k\)

然后你就分三种情况转:
\(0\) 次:那两边都要被挡住,对面的数组各自都要选一个拿出来,所以要乘上 \(j*j\);然后挡的位置少了一个。
\(1\) 次:那要选一边挡住,或者它们两个自己配对,所以是 \(j+j+1\);能挡的位置其实每边,毕竟你少了一个有多了一个相当于没边。
\(2\) 次:不用挡;能拿来挡的位置多了一个。

代码

#include<cstdio>
#define ll long long
#define mo 1000000007

using namespace std;

int n, k;
ll f[72][72][5001];

int main() {
	scanf("%d %d", &n, &k);
	
	f[n + 1][0][0] = 1;
	for (int i = n + 1; i >= 2; i--) {
		for (int j = 0; j <= n - i + 1; j++)
			for (int k = 0; k <= 5000; k++)
				if (f[i][j][k]) {
					(f[i - 1][j + 1][k + (i - 1) + (i - 1)] += f[i][j][k]) %= mo;
					(f[i - 1][j][k + (i - 1)] += f[i][j][k] * (j + j + 1) % mo) %= mo;
					if (j) (f[i - 1][j - 1][k] += f[i][j][k] * j % mo * j % mo) %= mo;
				} 
	}
	ll ans = 0;
	for (int i = k; i <= 5000; i++) (ans += f[1][0][i]) %= mo;
	for (int i = 1; i <= n; i++) ans = ans * i % mo;
	printf("%lld", ans);
	
	return 0;
}
posted @ 2022-02-25 21:39  あおいSakura  阅读(27)  评论(0编辑  收藏  举报