[TopCoder 12141] SweetFruits(折半搜索 + 矩阵树定理) | 错题本

题目

[TopCoder 12141] SweetFruits

题目大意

给出 n   ( n ≤ 40 ) n\ (n \leq 40) n (n40) 个水果,每个水果可能是“甜”的或“不甜”的,每个甜的水果有一个甜度值,特别的,为了便于读入,输入中不甜的水果甜度值为 − 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,,fk1 从里面减掉,就只剩下“编号为 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=Tkj=1k1fj 至于求 T k T_k Tk,直接 矩阵树定理 。具体来说就是钦定 1 ∼ k 1 \sim k 1k 为真甜水果 { x } \{x\} {x} k + 1 ∼ m k + 1 \sim m k+1m 为半甜水果 { y } \{y\} {y} m + 1 ∼ n m + 1 \sim n m+1n 为不甜水果 { 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;
	}
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值