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}\)。
不难得出状态转移方程:
上面一个是考虑现在就把 \(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,结合上面的贡献式,转移是显然的。
巧妙的是,\(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;
}

浙公网安备 33010602011771号