Topcoder 12141 - SweetFruits(矩阵树定理+折半搜索+二项式反演)

题面传送门

考虑枚举哪些水果是真甜的,设该集合为 \(S\),那么可以发现,当我们固定 \(S\) 时,连边方案数只与 \(S\) 的大小有关,具体来说,设 \(f_x\) 表示在所有甜味值不是 \(-1\) 的水果中有 \(x\) 个是真甜的方案数,那么对于一个具体的 \(S\),满足所有真甜水果的集合恰好是 \(S\) 的生成树个数就是 \(f_{|S|}\)

首先考虑怎么求 \(f_x\),设 \(m\) 表示甜味值不是 \(-1\) 的水果数量,那么我们考虑先求出 \(g_x\) 表示钦定 \(m-x\) 个水果不是真甜的方案数——不难发现这等价于求一张图的生成树个数,矩阵树定理解决。而不难发现恒等式 \(g_x=\sum\limits_{i=0}^{x}\dbinom{x}{i}·f_i\)。二项式反演一下可以求出 \(f\)

求出 \(f_x\)​ 之后,问题就转化为对 \(i=0,1,2,3,\cdots,m\)​,求有多少个大小为 \(i\) 的子集满足其中所有元素的和 \(\le\text{limit}\),注意到这题 \(n\) 数据范围不过 \(40\),因此考虑 meet-in-the-middle + 二分,这样可以实现 \(2^{n/2}·n^2\) 求解这部分。

总复杂度 \(n^4+2^{n/2}·n^2\)

const int MAXN = 40;
const int MAXP = 1048576;
const int MOD = 1e9 + 7;
int qpow(int x, int e) {
	int ret = 1;
	for (; e; e >>= 1, x = 1ll * x * x % MOD) if (e & 1) ret = 1ll * ret * x % MOD;
	return ret;
}
int n, a[MAXN + 3], cnt = 0, b[MAXN + 3], m, X;
int v[MAXN + 3][MAXN + 3], c[MAXN + 5][MAXN + 5];
int F[MAXN + 3], G[MAXN + 3];
int getdet() {
	int sgn = 1;
	for (int i = 1; i < n; i++) {
		int t = i;
		for (int j = i; j < n; j++) if (v[j][i]) t = j;
		if (t != i) {
			sgn = MOD - sgn;
			for (int j = i; j < n; j++) swap(v[t][j], v[i][j]);
		}
		int iv = qpow(v[i][i], MOD - 2);
		for (int j = i + 1; j < n; j++) {
			int mul = 1ll * (MOD - v[j][i]) * iv % MOD;
			for (int k = i; k < n; k++) v[j][k] = (v[j][k] + 1ll * mul * v[i][k]) % MOD;
		}
	}
	int res = sgn;
	for (int i = 1; i < n; i++) res = 1ll * res * v[i][i] % MOD;
	return res;
}
int sum1[MAXP + 5], sum2[MAXP + 5];
vector<int> sumv[MAXN + 5];
int hib[MAXP + 5];
int solve() {
//	printf("%d\n", n);
	for (int i = 1; i <= MAXN / 2; i++) hib[1 << i] = i;
	for (int i = 1; i <= n; i++) {
		if (a[i] == -1) cnt++;
		else b[++m] = a[i];
	}
	for (int i = 0; i <= m; i++) {
		memset(v, 0, sizeof(v));
		for (int j = 1; j <= n; j++) for (int k = 1; k < j; k++) {
			if (k <= m - i && j <= n - cnt) continue;
			v[j - 1][j - 1]++; v[k - 1][k - 1]++;
			v[j - 1][k - 1] = (v[j - 1][k - 1] - 1 + MOD) % MOD;
			v[k - 1][j - 1] = (v[k - 1][j - 1] - 1 + MOD) % MOD;
		}
//		for (int j = 1; j < n; j++) for (int k = 1; k < n; k++)
//			printf("%d%c", v[j][k], " \n"[k == n - 1]);
		F[i] = getdet();
	}
//	for (int i = 0; i <= m; i++) printf("%d\n", F[i]);
	for (int i = 0; i <= m; i++) {
		c[i][0] = 1;
		for (int j = 1; j <= i; j++) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % MOD;
	}
	for (int i = 0; i <= m; i++) for (int j = 0; j <= i; j++) {
		int coef = 1ll * c[i][j] * F[j] % MOD;
		if ((i - j) & 1) G[i] = (G[i] - coef + MOD) % MOD;
		else G[i] = (G[i] + coef) % MOD;
	}
//	for (int i = 0; i <= m; i++) printf("%d\n", G[i]);
	int lim = m >> 1, res = 0;
	for (int i = 1; i < (1 << lim); i++) sum1[i] = sum1[i & (i - 1)] + b[hib[i & (-i)] + 1];
	for (int i = 1; i < (1 << m - lim); i++) sum2[i] = sum2[i & (i - 1)] + b[lim + 1 + hib[i & (-i)]];
	for (int i = 0; i < (1 << m - lim); i++) sumv[__builtin_popcount(i)].pb(sum2[i]);
	for (int i = 1; i < m - lim; i++) sort(sumv[i].begin(), sumv[i].end());
	for (int i = 0; i < (1 << lim); i++) if (sum1[i] <= X) {
		for (int j = 0; j <= m - lim; j++) {
			int pos = upper_bound(sumv[j].begin(), sumv[j].end(), X - sum1[i]) - sumv[j].begin();
//			printf("%d %d %d\n", i, j, pos);
			res = (res + 1ll * pos * G[__builtin_popcount(i) + j]) % MOD;
		}
	}
	return res;
}
struct SweetFruits {
	int countTrees(vector<int> vec, int limit) {
		n = vec.size(); X = limit;
		for (int i = 1; i <= n; i++) a[i] = vec[i - 1];
		return solve();
	}
};
#ifdef LOCAL
SweetFruits pro;
int main() {
	int _n, _X; scanf("%d%d", &_n, &_X); vector<int> vec;
	for (int i = 1, x; i <= _n; i++) scanf("%d", &x), vec.pb(x);
	printf("%d\n", pro.countTrees(vec, _X));
	return 0;
}
#endif
posted @ 2022-01-12 22:32  tzc_wk  阅读(89)  评论(0)    收藏  举报