「JOI Open 2016」摩天大楼

\(\texttt{题面}\)

题意: 给定一个序列 \(A\)\(A\) 中的元素互不相同,求有多少个 \(A\) 的排列满足 \(\sum\limits_{i=2}^n|A_i - A_{i-1}|\le L\),答案对 \(10^9 + 7\) 取模。

\(\text{Limits:}\) \(1\le n\le100,1\le L,A_i\le 1000\)

这个绝对值很烦,考虑把它去掉,利用一个技巧:微元贡献法(网上看到的

首先将 \(A\) 排序,从小到大插入这 \(n\) 个数。

假设要插入第 \(i\) 个数:

设与它相邻的数(之一)为 \(A_j\),则贡献为 \(A_i - A_j\),但是这样并不好求,考虑将它转化为 \(A_i - A_j = \sum\limits_{k=j}^{i-1}A_{k + 1} - A_k\),这样的话,我们可以提前计算每一个 \(A_{i + 1} - A_i\) 对总和的贡献。

\(dp(i,j,k,l)\) 表示插入了前 \(i\) 个数(排序后),形成了 \(j\) 区间,目前 对 \(\sum |A_i - A_{i-1}|\) 的贡献为 \(k\),有 \(l=0/1/2\) 个区间靠在边界上的方案数。

考虑新插入一个数 \(A_{i + 1}\)\(A_{i+1} - A_i\) 的贡献为 \((A_{i+1} - A_i) * (j * 2 - l)\)

\(\text{Ps:}\) \(j * 2 - l\) 的含义是能与 \(\le A_i\) 的数相邻的位置。(因为只有 \(j\le k <i\)\(A_{k+1} - A_k\) 才能有贡献)

记转移后的 \(k\)\(k'=k + (A_{i+1} - A_i) * (j * 2 - l)\),则有转移:

\(\bullet\) 新增一个不靠墙的区间: \(dp(i+1,j+1,k',l) \longleftarrow dp(i,j,k,l) * (j + 1 - l)\)

\(\bullet\) 新增一个靠墙的区间: \(dp(i + 1,j + 1, k', l + 1) \longleftarrow dp(i,j,k,l) * (2-l)\)

\(\bullet\) 连在其中一个已有的区间上: \(dp(i + 1,j,k',l) \longleftarrow dp(i,j,k,l) * (2 * j - l)\)

\(\bullet\) 连接两个区间: \(dp(i + 1,j-1,k',l) \longleftarrow dp(i,j,k,l) * (j-1)\)

\(\bullet\) 连接一个区间和墙: \(dp(i +1,j,k',l+1) \longleftarrow dp(i,j,k,l) * (2 - l)\)

时间复杂度 \(O(n^2L)\)

\(\texttt{Code:}\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int cmd = 1e9 + 7;
const int N = 105;
const int M = 1e3 + 5;
int n, L, a[N], dp[2][N][M][3];
void add(int &x, ll y) {x = (x + y) % cmd;}
int main() {
	scanf("%d%d", &n, &L);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	if (n == 1) {puts("1"); return 0;}
	sort(a + 1, a + n + 1);
	int cur = 1;
	dp[0][0][0][0] = 1;
	for (int i = 0; i < n; i++) {
		cur ^= 1;
		for (int j = 0; j <= i + 1; j++)
		for (int k = 0; k <= L; k++)
		for (int l = 0; l <= 2; l++)
			dp[cur ^ 1][j][k][l] = 0;
		for (int j = 0; j <= i; j++)
		for (int k = 0; k <= L; k++)
		for (int l = 0; l <= 2; l++) {
			if (!dp[cur][j][k][l]) continue;
			int tk = k + (2 * j - l) * (a[i + 1] - a[i]);
			if (tk > L) continue;
			add(dp[cur ^ 1][j + 1][tk][l], 1ll * dp[cur][j][k][l] * (j + 1 - l));
			if (l < 2) add(dp[cur ^ 1][j + 1][tk][l + 1], 1ll * dp[cur][j][k][l] * (2 - l));
			add(dp[cur ^ 1][j][tk][l], 1ll * dp[cur][j][k][l] * (2 * j - l));
			if (j) add(dp[cur ^ 1][j - 1][tk][l], 1ll * dp[cur][j][k][l] * (j - 1));
			if (l < 2 && j) add(dp[cur ^ 1][j][tk][l + 1], 1ll * dp[cur][j][k][l] * (2 - l));
		}
	}
	int ans = 0;
	for (int i = 0; i <= L; i++)
		add(ans, dp[cur ^ 1][1][i][2]);
	printf("%d", ans);
	return 0;
}
posted @ 2021-10-15 10:04  klii  阅读(183)  评论(0)    收藏  举报