1. 程式人生 > 其它 >Solution -「HDU 6643」Ridiculous Netizens

Solution -「HDU 6643」Ridiculous Netizens

\(\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;
}