题目
题目大意
给出 n ( n ≤ 40 ) n\ (n \leq 40) n (n≤40) 个水果,每个水果可能是“甜”的或“不甜”的,每个甜的水果有一个甜度值,特别的,为了便于读入,输入中不甜的水果甜度值为 − 1 -1 −1。现在要求将 n n n 个水果连成一棵树,这个树的甜度值为“真甜”的水果的甜度值之和。“真甜”的水果定义如下:若一个甜的水果与另外至少一个甜的水果通过一条边直接相连,那么这个水果是“真甜”的。求所有方案中,甜度值不超过 p p p 的树有多少个。每个水果各不相同,但甜度值可能相同。
分析
除了上面的“真甜”,我们定义一个水果是“半甜”的当且仅当它是甜的但不与其他任何一个甜的水果通过一条边直接相连。
将问题分成两部分:
- 选出 k k k 个甜水果,他们的甜度值之和不超过 p p p 的方案数;
- 仅编号为 1 , 2 , ⋯ , k 1, 2, \cdots, k 1,2,⋯,k 的点为真甜水果的有标号无根树的方案数。
于是把这两部分对应乘起来再相加就是最后的答案。
对于第一部分,用折半搜索即可。具体来说,设 m m m 为甜水果的数量,两次二进制枚举,将前一半( ⌊ m 2 ⌋ \lfloor\frac{m}{2}\rfloor ⌊2m⌋ 个)的所有情况和后一半( ⌈ m 2 ⌉ \lceil\frac{m}{2}\rceil ⌈2m⌉ 个)的所有情况的甜度值之和分别存入数组(由于要按水果数量分类,所以这个是二维数组)。对两个数组排序后,枚举左半部分和右半部分分别取多少个,然后双指针扫一遍即可。具体可以看代码。
对于第二部分,记“仅编号为 1 , 2 , ⋯ , k 1, 2, \cdots, k 1,2,⋯,k 的点为真甜水果的有标号无根树的方案数”为 f k f_k fk。直接求“恰好”不好办,肯定要往矩阵树定理上靠,所以考虑求“编号为 k + 1 , k + 2 , ⋯ , n k + 1, k + 2, \cdots, n k+1,k+2,⋯,n 的水果一定不是真甜水果的方案数”记为 T k T_k Tk,这样编号为 1 , 2 , ⋯ , k 1, 2, \cdots, k 1,2,⋯,k 的点可能是真甜水果或半甜水果,因此我们把 f 1 , f 2 , ⋯ , f k − 1 f_1, f_2, \cdots, f_{k - 1} f1,f2,⋯,fk−1 从里面减掉,就只剩下“编号为 1 , 2 , ⋯ , k 1, 2, \cdots, k 1,2,⋯,k 的点全部是真甜水果”的情况了。即 f k = T k − ∑ j = 1 k − 1 f j f_k = T_k - \sum_{j = 1}^{k - 1}f_{j} fk=Tk−j=1∑k−1fj 至于求 T k T_k Tk,直接 矩阵树定理 。具体来说就是钦定 1 ∼ k 1 \sim k 1∼k 为真甜水果 { x } \{x\} {x}, k + 1 ∼ m k + 1 \sim m k+1∼m 为半甜水果 { y } \{y\} {y}, m + 1 ∼ n m + 1 \sim n m+1∼n 为不甜水果 { z } \{z\} {z}。 { x } \{x\} {x} 与 { z } \{z\} {z} 相互全部连边, { y } \{y\} {y} 与 { z } \{z\} {z} 相互全部连边。
代码
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
const int MAXN = 40;
const int MOD = 1000000007;
inline int Add(int x, int y) {
x += y; if (x >= MOD) x -= MOD; return x;
}
inline int Sub(int x, int y) {
x -= y; if (x < 0) x += MOD; return x;
}
inline int Mul(int x, int y) {
return (long long)x * y % MOD;
}
int Pow(int x, int y) {
int ret = 1;
while (y) {
if (y & 1)
ret = Mul(ret, x);
x = Mul(x, x);
y >>= 1;
}
return ret;
}
int N, MaxV, M;
int V[MAXN + 5];
int Num[MAXN + 5];
std::vector<int> A[MAXN + 5], B[MAXN + 5];
int Dp[MAXN + 5];
int C[MAXN + 5][MAXN + 5];
int Mat[MAXN + 5][MAXN + 5];
void AddEdge(int u, int v) {
Mat[u][u]++, Mat[v][v]++;
Mat[u][v]--, Mat[v][u]--;
}
int Det(int n) {
n--;
int ret = 1;
for (int i = 1; i <= n; i++) {
int p = i;
for (int k = i + 1; k <= n; k++)
if (Mat[k][i]) {
p = k;
break;
}
if (!Mat[p][i]) return 0;
if (p != i) {
ret = Sub(0, ret);
std::swap(Mat[p], Mat[i]);
}
ret = Mul(ret, Mat[i][i]);
int inv = Pow(Mat[i][i], MOD - 2);
for (int j = i + 1; j <= n; j++) {
int tmp = Mul(Mat[j][i], inv);
for (int k = i; k <= n; k++)
Mat[j][k] = Sub(Mat[j][k], Mul(Mat[i][k], tmp));
}
}
return ret;
}
int Count(int k) {
// 1 ~ k 号点真甜, k + 1 ~ M 号点半甜, M + 1 ~ N 号点不甜
// 半甜只能与不甜的连边, 其他的全部连
memset(Mat, 0, sizeof Mat);
for (int i = 1; i <= N; i++)
for (int j = i + 1; j <= N; j++)
if (j > M || (i <= k && j <= k))
AddEdge(i, j);
return Det(N);
}
class SweetFruits {
public:
int countTrees(std::vector<int> sweetness, int maxSweetness) {
N = sweetness.size();
for (int i = 0; i < N; i++)
if (~sweetness[i])
V[++M] = sweetness[i];
MaxV = maxSweetness;
int cntA = M / 2, cntB = M - M / 2;
int lim = (1 << cntA) - 1;
for (int i = 0; i <= lim; i++) {
int sum = 0, cnt = 0;
for (int j = 1; j <= cntA; j++)
if ((i >> (j - 1)) & 1)
sum += V[j], cnt++;
A[cnt].push_back(sum);
}
lim = (1 << cntB) - 1;
for (int i = 0; i <= lim; i++) {
int sum = 0, cnt = 0;
for (int j = 1; j <= cntB; j++)
if ((i >> (j - 1)) & 1)
sum += V[M / 2 + j], cnt++;
B[cnt].push_back(sum);
}
for (int i = 0; i <= cntA; i++)
std::sort(A[i].begin(), A[i].end());
for (int i = 0; i <= cntB; i++)
std::sort(B[i].begin(), B[i].end());
for (int lft = 0; lft <= cntA; lft++)
for (int rgt = 0; rgt <= cntB; rgt++) {
int i = A[lft].size() - 1, j = 0, k = lft + rgt;
while (i >= 0) {
int x = A[lft][i--];
while (j < int(B[rgt].size()) && x + B[rgt][j] <= MaxV)
j++;
Num[k] = Add(Num[k], j);
}
}
for (int i = 0; i <= M; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = Add(C[i - 1][j - 1], C[i - 1][j]);
}
int Ans = 0;
for (int i = 0; i <= M; i++) {
Dp[i] = Count(i);
for (int j = 0; j < i; j++)
Dp[i] = Sub(Dp[i], Mul(Dp[j], C[i][j]));
Ans = Add(Ans, Mul(Dp[i], Num[i]));
}
return Ans;
}
};