\(\mathcal{Description}\)
Link.
给定一棵含有 \(n\) 个结点的树,点 \(u\) 有点权 \(w_u\),求树上非空连通块的数量,使得连通块内点权积 \(\le m\)。
\(n\le2\times10^3\),\(m\le10^6\),\(w_u\in[1,m]\),数据组数 \(T\le10\)。
\(\mathcal{Solution}\)
很明显是点分,每次考虑跨当前分治重心 \(r\) 的所有连通块对答案的贡献。问题变为:求树上以 \(r\) 为根的满足条件的连通块数量。
一个简单的想法是以子树为子问题树上 DP,但是点权积的状态空间与子树大小完全无关,子树与子树的合并反而更加浪费时间,这提示我们,应该设计一种仅有单点更新的 DP 状态——以 DFN 为子问题 DP。
另一方面,由于运算全部是乘法,可以考虑整除分块的储存方式压缩状态树。令 \(f(u,i)\) 表示当 DFS 进行到某一时刻时,以 \(u\) 子树内已经被搜过的点为最大 DFN 点的连通块中,点权积在整除分块后被映射到 \(i\) 的方案数。进入 \(u\) 子树时用 \(u\) 的父亲更新 \(f(u)\),退出 \(u\) 子树时将 \(f(u)\) 上传给 \(u\) 的父亲。设树的大小为 \(s\),DP 的复杂度为 \(\mathcal O(s\sqrt m)\)。
最终,算法复杂度为 \(\mathcal O(Tn\sqrt m\log n)\)。
\(\mathcal{Code}\)
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
typedef long long LL;
const int MAXN = 2e3, MOD = 1e9 + 7, THRES = 1e3;
int n, m, thres, ecnt, val[MAXN + 5], head[MAXN + 5];
int siz[MAXN + 5], wgt[MAXN + 5], ans;
int f[MAXN + 5][THRES * 2 + 5], g[MAXN + 5][THRES * 2 + 5];
struct Edge { int to, nxt; } graph[MAXN * 2 + 5];
bool vis[MAXN + 5];
inline void chkmax(int& u, const int v) { u < v && (u = v); }
inline int imin(const int u, const int v) { return u < v ? u : v; }
inline void addeq(int& u, const int v) { (u += v) >= MOD && (u -= MOD); }
inline void link(const int u, const int v) {
graph[++ecnt] = { v, head[u] }, head[u] = ecnt;
graph[++ecnt] = { u, head[v] }, head[v] = ecnt;
}
inline void findG(const int u, const int fa, const int all, int& rt) {
siz[u] = 1, wgt[u] = 0;
for (int i = head[u], v; i; i = graph[i].nxt) {
if (!vis[v = graph[i].to] && v != fa) {
findG(v, u, all, rt), siz[u] += siz[v];
chkmax(wgt[u], siz[v]);
}
}
chkmax(wgt[u], all - siz[u]);
if (!rt || wgt[rt] > wgt[u]) rt = u;
}
inline void getDP(const int u, const int fa) {
int *fcur = f[u], *ffa = f[fa];
rep (i, 0, thres << 1) fcur[i] = 0;
if (!fa) fcur[val[u] <= thres ? val[u] : thres + m / val[u]] = 1;
else {
rep (i, 0, imin(thres, m / val[u])) {
int t = i * val[u];
addeq(fcur[t <= thres ? t : thres + m / t], ffa[i]);
}
rep (i, val[u], thres) {
addeq(fcur[thres + i / val[u]], ffa[thres + i]);
}
}
for (int i = head[u], v; i; i = graph[i].nxt) {
if (!vis[v = graph[i].to] && v != fa) {
getDP(v, u);
}
}
if (fa) rep (i, 0, thres << 1) addeq(ffa[i], fcur[i]);
}
inline void solve(const int u) {
// printf("!%d\n", u);
vis[u] = true, getDP(u, 0);
rep (i, 0, thres << 1) addeq(ans, f[u][i]);
for (int i = head[u], v, rt; i; i = graph[i].nxt) {
if (!vis[v = graph[i].to]) {
findG(v, 0, siz[v], rt = 0), solve(rt);
}
}
}
inline void allClear() {
ans = ecnt = 0;
rep (i, 1, n) head[i] = vis[i] = 0;
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d %d", &n, &m), thres = int(sqrt(1. * m));
allClear();
rep (i, 1, n) scanf("%d", &val[i]);
rep (i, 2, n) { int u, v; scanf("%d %d", &u, &v), link(u, v); }
int rt = 0; findG(1, 0, n, rt);
solve(rt), printf("%d\n", ans);
}
return 0;
}