8.31 模拟赛 T2

题意:给定 \(n\) 和长度为 \(n\) 的数组 \(\{a_n\}\),要求将 \(1,2,\dots,n\) 划分成若干个小组(不要求连续)。要求,对于每个 \(i\),其所在的组中元素个数 \(\le a_i\)。求方案数,对 \(10^9+7\) 取模。\(n \le 2000,1 \le a_i \le n\),时限 0.5s。
两种方案被认为是不同的,if and only if 存在 \(i,j\),在一种方案中在同一组中,另一种方案中不在同一组中。

算法 1

注意到对于同一组中 \(a_i\) 的限制,只需要考虑 \(a_i\) 最小的即可。
不妨将 \(a_i\) 从大到小排序。

然后就是,我们不关心具体分了多少组,只关心每组的元素个数。

\(f_{i,j}\) 表示考虑到前 \(i\) 个数,考虑完 \(i\) 后有 \(j\) 个空位,总共的方案数。初态 \(f_{0,0}=1\),答案就是 \(f_{n,0}\)

不难得出状态转移方程:

\[f_{i,j} \gets f_{i-1,j+t-1} \times \binom{j+t-1}{t-1},1 \le t \le a_i,j+t \le n \]

\[f_{i,j} \gets f_{i-1,j-1} \]

上面一个是考虑现在就把 \(i\) 确定分什么组。枚举可能的组大小 \(t\),根据状态,\(1 \sim i-1\)\(j+t-1\) 个空位。任选剩下的 \(t-1\) 个得到转移方程。
下面一行就是暂缓 \(i\) 的分组情况。

时间复杂度 \(O(n^3)\)。这里把组合数拆开就是一个差卷积的形式,用 FFT 可以优化到 \(O(n^2 \log n)\)

道理我都懂,可是你模数开成 1e9+7 是几个意思??

常数巨大,并不足以通过这道题。

算法 2

我们换一个角度思考问题。

发现每一种分组方案可以看做给一个 \(b\) 数组填数,表示 \(i\) 所在组的元素个数。那么分组就可以视为给每个 \(b_i\) 填一个 \([1,a_i]\) 的数。记 \(j\)\(b\) 中出现了 \(t_b\) 次,一个 \(b\) 数组是有效的等价于所有 \(t_i\)\(i\) 的倍数。

而对于一个确定的 \(b\) 数组,其贡献就是,枚举每个取值 \(i\),要将这些数分进相同的 \(\frac {t_i} i\) 个盒子。盒子不同就是广义组合数 \(\binom{t_i}{i, i, \dots, i}\),盒子相同就是:$$\prod \frac {\binom{t_i}{i, i, \dots, i}}{(\frac{t_i}i)!}$$

于是考虑怎么 DP。
对于 \(j\) 这个取值,它只能填在 \(a_i \ge j\) 的位置中。
\(c_i\) 表示 \(i\)\(a\) 数组里的出现次数。

\(f_{i,j}\) 表示在 \(b\) 中填入了取值为 \(i,i+1,\dots, n\) 的数,填完后还有 \(j\) 个空位可以填,的方案数。
所谓有 \(j\) 个空位,来源就是:

对于 \(j\) 这个取值,它只能填在 \(a_i \ge j\) 的位置中。

考虑转移。对于 \(f_{i,j}\),要求 \(i\) 这个取值填入了 \(i\) 的倍数个,故枚举 \(k\) 表示 \(i\) 这个取值填入了 \(ik\) 个。仿照算法 1,结合上面的贡献式,转移是显然的。

\[f_{i,j} \gets f_{i+1, j-c_i+ik} \times \binom{j+ik}{ik} \times \frac{(ik)!}{(i!)^k \times k!}, 0 \le j-c_i+ik \le n \]

巧妙的是,\(k\) 的取值只有 \(O(\left \lfloor \frac n i \right \rfloor)\) 个。所以总复杂度是调和级数级别的,\(O(n^2 \log n)\)

#include <bits/stdc++.h>
using namespace std;

//#define filename "group" 
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
//#define multi_cases 1

#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int> 
#define ull unsigned long long
#define all(v) v.begin(), v.end()
#define upw(i, a, b) for(int i = (a); i <= (b); ++i)
#define dnw(i, a, b) for(int i = (a); i >= (b); --i)

template<class T> bool vmax(T &a, T b) { return b > a ? a = b, true : false; }
template<class T> bool vmin(T &a, T b) { return b < a ? a = b, true : false; }
template<class T> void clear(T &x) { T().swap(x); }

const int N = 2002;

const int P = 1000000007;
void vadd(int &a, int b) { a += b; if(a >= P) a -= P; }

int qpow(int a, int b, int P = ::P) {
	int res = 1;
	while(b) {
		if(b & 1) res = 1ll * res * a % P;
		a = 1ll * a * a % P;
		b >>= 1;
	}
	return res;
}

int fac[N], fac_inv[N], inv[N];
int C(int n, int m) {
	if(n < 0 || m < 0 || n < m) return 0;
	return 1ll * fac[n] * fac_inv[m] % P * fac_inv[n-m] % P;
}

int n, a[N], c[N];
int f[N][N];

void WaterM() {
	cin >> n;
	upw(i, 1, n) cin >> a[i];
	upw(i, 1, n) ++c[a[i]];
	
	fac[0] = fac_inv[0] = inv[1] = 1;
	upw(i, 2, n) inv[i] = 1ll * (P - P / i) * inv[P % i] % P;
	upw(i, 1, n) {
		fac[i] = 1ll * fac[i-1] * i % P;
		fac_inv[i] = 1ll * fac_inv[i-1] * inv[i] % P;
	}
	
	f[n+1][0] = 1;
	dnw(i, n, 1) upw(j, 0, n) upw(k, 0, n / i) {
		int t = j - c[i] + i * k;
		if(0 <= t && t <= n)
			vadd(f[i][j], 1ll * f[i+1][t] * C(t + c[i], i * k) % P * fac[i * k] % P * qpow(fac_inv[i], k) % P * fac_inv[k] % P);
	}
	cout << f[1][0] << '\n';
}

signed main() {
#ifdef filename
	FileOperations();
#endif
	
	signed _ = 1;
#ifdef multi_cases
	scanf("%d", &_);
#endif
	while(_--) WaterM();
	return 0;
}


posted @ 2025-09-04 22:51  Water_M  阅读(8)  评论(0)    收藏  举报