题目
点这里看题目。
分析
我们可以先将树变成有根树,这样我们可以计算包含某个点的连通块数量,求和自然是树上所有连通块的数量。
那么,如果一个结点被连通块包含,则它的祖先也必须被包含。自顶向下的 DP 难以使用 DFS 解决,因此我们可以在 DFS 序上进行 DP,这样选择一个结点就是转移到 DFS 上的下一个,而跳过一个结点就相当于跳过整棵子树,快速跳转即可。
一个容易想到的 DP 是,设 \(f_{i,j}\) 表示考虑 DFS 上前 \(i\) 个结点后,当前的积为 \(j\) 时连通块个数,但是硬转移会 T。注意到,真正影响转移的是 \(\lfloor\frac{m}{j}\rfloor\) 而不是 \(j\),而 \(\lfloor\frac m j\rfloor\) 只有 \(O(\sqrt m)\) 种取值,因此我们压缩状态,设 \(g_{i,j}\) 表示考虑 DFS 上前 \(i\) 个结点后,当前的积除 \(m\) 后取整得到 \(j\) 时的连通块个数,转移便是 \(O(n\sqrt m)\) 的。
注意到,根的选择也会影响到转移的效率,容易想到选取重心效率更高。复杂度便是 \(O(n\sqrt m\log n)\)。
小结:
- 思考的思路应当自然而清晰。虽然算法最终实现是点分治的样子,但是实际上思考地时候自底向上会更加自然,也容易理解。
- 注意 DP 过程中在 DFS 序上 DP 和优化 DP 状态的两个部分,比较有借鉴的价值。
代码
#include <cstdio>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int mod = 1e9 + 7;
const int MAXN = 2005, MAXM = 1e6 + 5;
template<typename _T>
void read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
void write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
_T MAX( const _T a, const _T b ) {
return a > b ? a : b;
}
struct Edge {
int to, nxt;
}Graph[MAXN << 1];
int dp[MAXN][MAXN];
int val[MAXN], mp[MAXM];
int tot = 0;
int sub[MAXN], seq[MAXN];
int ID = 0;
int head[MAXN], wei[MAXN];
int siz[MAXN]; bool vis[MAXN];
// siz[] is for division process
int ans = 0;
int N, M, cnt = 1;
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline void Upt( int &x, const int v ) { x = Add( x, v ); }
void AddEdge( const int from, const int to ) {
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt;
}
void Clear() {
ans = 0, cnt = 1;
rep( i, 1, N ) {
head[i] = 0, vis[i] = false;
}
}
int GetCen( const int u, const int fa, const int all ) {
siz[u] = 1; int mx = 0, ret = 0;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ( v = Graph[i].to ) ^ fa && ! vis[v] ) {
ret |= GetCen( v, u, all );
siz[u] += siz[v], mx = MAX( mx, siz[v] );
}
if( MAX( mx, all - siz[u] ) * 2 <= all ) ret = u;
return ret;
}
// DO NOT CONFUSE sub[] with siz[].
void DFS( const int u, const int fa ) {
sub[u] = 1, seq[++ ID] = u;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ( v = Graph[i].to ) ^ fa && ! vis[v] )
DFS( v, u ), sub[u] += sub[v];
}
void Calc( const int rt ) {
ID = 0, DFS( rt, 0 );
rep( i, 0, ID )
rep( j, 0, tot )
dp[i][j] = 0;
dp[1][mp[M / wei[rt]]] = 1;
for( int i = 1 ; i < ID ; i ++ )
for( int j = 1 ; j <= tot ; j ++ ) {
if( ! dp[i][j] ) continue;
int cur = val[j], nxt = seq[i + 1];
Upt( dp[i + sub[nxt]][j], dp[i][j] );
if( wei[nxt] <= cur )
Upt( dp[i + 1][mp[cur / wei[nxt]]], dp[i][j] );
}
rep( j, 1, tot ) ans = Add( ans, dp[ID][j] );
}
void Divide( int u, const int all ) {
u = GetCen( u, 0, all );
vis[u] = true, Calc( u );
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ! vis[v = Graph[i].to] )
Divide( v, siz[v] > siz[u] ? all - siz[u] : siz[v] );
}
int main() {
int T; read( T );
while( T -- ) {
read( N ), read( M ), Clear();
rep( i, 1, N ) read( wei[i] );
rep( i, 1, N - 1 ) {
int u, v; read( u ), read( v );
AddEdge( u, v ), AddEdge( v, u );
}
tot = 0;
for( int l = 1, tmp ; l <= M ; l = M / tmp + 1 )
tmp = M / l, val[mp[tmp] = ++ tot] = tmp;
Divide( 1, N );
write( ans ), putchar( '\n' );
}
return 0;
}