「LOJ6072」「2017 山东一轮集训 Day5」苹果树-矩阵树定理

Description

链接

Solution

考虑把问题转化为两问。

第一问为对于 k [ 1 , n ] k \in [1,n] ,选出 k k 个权值和 l i m \le lim 的水果的方案数。

第二问为把这 k k 个水果钦定为真香的生成树个数。把水果分为真香,香但不真香,不香的水果。其中第一类水果向一三类水果连边,第二类水果向第三类水果连边,第三类水果任意连边,矩阵树定理统计即可。注意求出的是至多 k k 个真香的方案数,还要容斥,容斥系数为组合数。

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

typedef long long lint;
const int maxn = 45, mod = 1e9 + 7;

int n, m, lim, s[maxn];
vector<int> vec1[maxn], vec2[maxn];
int C[maxn][maxn];
int ans[maxn], g[maxn][maxn], Ans;

inline int gi()
{
	char c = getchar(); bool f = 1;
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') c = getchar(), f = 0;
	int sum = 0;
	while ('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return f ? sum : -sum;
}

inline int inc(int a, int b) {return a + b >= mod ? a + b - mod : a + b;}
inline int dec(int a, int b) {return a - b < 0 ? a - b + mod : a - b;}
inline int mul(int a, int b) {return (lint)a * b % mod;}

inline lint calc(int x, int y)
{
	lint res = 0;
	for (int i = 0, j = vec2[y].size() - 1, siz = vec1[x].size(); i < siz; ++i) {
		while ((~j) && vec1[x][i] + vec2[y][j] > lim) --j;
		res += j + 1;
	}
	return res;
}

#define add(i, j) --g[i][j], ++g[i][i]

int det(int a[maxn][maxn], int n)
{
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j) a[i][j] = (a[i][j] + mod) % mod;
	int ans = 1;
	for (int i = 1; i <= n; ++i) {
		for (int j = i + 1; j <= n; ++j) 
			while (a[j][i]) {
				int t = a[i][i] / a[j][i];
				for (int k = i; k <= n; ++k) a[i][k] = dec(a[i][k], mul(t, a[j][k]));
				swap(a[i], a[j]);
				ans = mod - ans;
			}
		ans = mul(ans, a[i][i]);
		if (!ans) return 0;
	}
	return ans;
}

int main()
{
	n = gi(); lim = gi();
	for (int i = 1; i <= n; ++i) {
		s[i] = gi();
		if (~s[i]) s[++m] = s[i];
	}
	
	for (int i = 0; i < (1 << (m / 2)); ++i) {
		int x = 0, y = 0;
		for (int j = 1; j <= m / 2; ++j)
			if ((i >> (j - 1)) & 1) {
				++x; y += s[j];
			}
		vec1[x].push_back(y);
	}
	for (int i = 0; i < (1 << (m - m / 2)); ++i) {
		int x = 0, y = 0;
		for (int j = m / 2 + 1; j <= m; ++j)
			if ((i >> (j - m / 2 - 1)) & 1) {
				++x; y += s[j];
			}
		vec2[x].push_back(y);
	}

	for (int i = 1; i <= m / 2; ++i) sort(vec1[i].begin(), vec1[i].end());
	for (int i = 1; i <= m - m / 2; ++i) sort(vec2[i].begin(), vec2[i].end());

	C[0][0] = 1;
	for (int i = 1; i <= n; ++i) {
		C[i][0] = 1;
		for (int j = 1; j <= i; ++j) 
			C[i][j] = inc(C[i - 1][j - 1], C[i - 1][j]);
	}
	
	for (int k = 0; k <= n; ++k) {
		lint sum = 0;
		for (int i = 0; i <= k; ++i) sum += calc(i, k - i);
		memset(g, 0, sizeof(g));
		for (int i = 1; i <= k; ++i) {
			for (int j = 1; j <= k; ++j) add(i, j);
			for (int j = m + 1; j <= n; ++j) add(i, j);
		}
		for (int i = k + 1; i <= m; ++i)
			for (int j = m + 1; j <= n; ++j) add(i, j);
		for (int i = m + 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j) add(i, j);
		ans[k] = det(g, n - 1);
		for (int i = 0; i < k; ++i) ans[k] = dec(ans[k], mul(C[k][i], ans[i]));
		Ans = inc(Ans, mul(ans[k], sum % mod));
	}

	printf("%d\n", Ans);

	return 0;
}

猜你喜欢

转载自blog.csdn.net/DSL_HN_2002/article/details/85610254