「HDU6643」Ridiculous Netizens

题目

点这里看题目。

分析

我们可以先将树变成有根树,这样我们可以计算包含某个点的连通块数量,求和自然是树上所有连通块的数量。

那么,如果一个结点被连通块包含,则它的祖先也必须被包含。自顶向下的 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)\)

小结:

  1. 思考的思路应当自然而清晰。虽然算法最终实现是点分治的样子,但是实际上思考地时候自底向上会更加自然,也容易理解。
  2. 注意 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;
}
原文地址:https://www.cnblogs.com/crashed/p/15530973.html