【题目链接】
【思路要点】
首先,也是最重要的一点,我们发现若我们规定一些 点为 ,剩余的 点不为 ,可行的生成树的方案仅和我们规定为 的 点的数量有关。
因此问题被分成了两个:
计算出 表示我们规定 个 点为 ,可行的生成树的个数。
计算出 表示选取恰好 个 点,使得它们的权值之和不超过限制的方案数。
显然有
显然可以通过折半搜索 二分在 的时间内求出。
考虑如何计算 ,构建一个 个点的完全图,并删去两端都为 点并且有至少一端不是被选中的 个点中的点的边,用矩阵树定理求解其生成树的个数,记为 。
中计算了只有被选中的点与 点存在邻边的方案数,应当去掉存在被选中的点只与 点相连的方案数。
即 。
时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 45;
const int HALF = 21;
const int MAXS = 1.1e6;
const int INF = 1e9 + 5;
const int P = 1e9 + 7;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct info {int sum, sel; } half[MAXS];
int n, m, limit, ans, tmp, tot;
int a[MAXN][MAXN], val[MAXN];
int c[MAXN][MAXN], cnt[MAXN];
int pre[MAXS][HALF];
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
int gauss(int n) {
int f = 1;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++)
if (a[j][i] != 0) {
if (i != j) f = -f;
swap(a[i], a[j]);
break;
}
if (a[i][i] == 0) return 0;
int inv = power(a[i][i], P - 2);
for (int j = i + 1; j <= n; j++) {
int tmp = P - a[j][i];
if (tmp == P) continue;
tmp = 1ll * tmp * inv % P;
for (int k = i; k <= n; k++)
a[j][k] = (a[j][k] + 1ll * a[i][k] * tmp) % P;
}
}
int ans = (P + f) % P;
for (int i = 1; i <= n; i++)
ans = 1ll * ans * a[i][i] % P;
return ans;
}
void addedge(int x, int y) {
a[x][y] = a[y][x] = P - 1;
a[x][x]++, a[y][y]++;
}
void work(int pos, int sum, int sel) {
if (pos == tmp + 1) half[++tot] = (info) {sum, sel};
else {
work(pos + 1, sum, sel);
if (val[pos] != -1 && sum + val[pos] <= limit) work(pos + 1, sum + val[pos], sel + 1);
}
}
void find(int sum, int sel) {
int l = 1, r = tot;
while (l < r) {
int mid = (l + r + 1) / 2;
if (half[mid].sum + sum <= limit) l = mid;
else r = mid - 1;
}
for (int i = 0; i <= tmp; i++)
ans = (ans + 1ll * pre[l][i] * cnt[sel + i]) % P;
}
void getans(int pos, int sum, int sel) {
if (pos == n + 1) find(sum, sel);
else {
getans(pos + 1, sum, sel);
if (val[pos] != -1 && sum + val[pos] <= limit) getans(pos + 1, sum + val[pos], sel + 1);
}
}
bool cmp(info a, info b) {
return a.sum < b.sum;
}
int main() {
for (int i = 0; i < MAXN; i++)
c[i][0] = 1;
for (int i = 1; i < MAXN; i++)
for (int j = 1; j <= i; j++)
c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % P;
int T; read(T);
while (T--) {
read(n), read(limit), m = 0;
for (int i = 1; i <= n; i++) {
read(val[i]);
if (val[i] != -1) m++;
}
for (int i = 0; i <= m; i++) {
memset(a, 0, sizeof(a));
for (int j = 1; j <= n; j++)
for (int k = j + 1; k <= n; k++)
if (k > m || k <= i) addedge(j, k);
cnt[i] = gauss(n - 1);
for (int j = 0; j < i; j++)
cnt[i] = (cnt[i] - 1ll * c[i][j] * cnt[j] % P + P) % P;
}
ans = 0, tot = 0;
tmp = n / 2;
work(1, 0, 0);
sort(half + 1, half + tot + 1, cmp);
for (int i = 1; i <= tot; i++) {
memcpy(pre[i], pre[i - 1], sizeof(pre[i - 1]));
pre[i][half[i].sel]++;
}
getans(tmp + 1, 0, 0);
writeln(ans);
}
return 0;
}