[HDU6643]Ridiculous Netizens

壹、题目描述 ¶

  传送门 to HDU.

贰、题解 ¶

  如果没有 \(w_i=1\) 的情况,那么一个块的大小是决然不会超过 \(\log m\) 的,不过,现在出现的问题,是 \(w_i=1\) 导致块的大小可能很大。

  我们不妨先考虑如何统计包含一个固定点 \(rt\) 的合法块的个数,实际上只需要树 \(\rm DP\) 即可,定义状态 \(dp(i,w)\) 表示选了 \(i\) 这个点,容量为 \(w\) 的方案数。如果我们以 \(rt\) 为根,那么,一个点如果想要被选择,其父亲也应该在集合中,于是我们可以从其父亲转移到它自己,表示选择了它,没选择它子树节点的贡献,递归计算之后再加上儿子的贡献,表示选择了它以及它子树内的贡献。

  不过,这样做好像有点问题,第二维的大小达到了 \(m\),状态的总数就是 \(\mathcal O(nm)\) 的,甚至会超过空间限制,不过我们注意到 \(w=m\)\(w=m-1\) 实际上都只能再放入价值为 \(1\) 的数字,于是我们可以将这两种情况归为一类 —— 以最多还能放入的数字大小作为下标,这样的话,第二维的大小至多只有 \(\mathcal O(2\sqrt m)\) 种:

  • 对于 \(i\le \sqrt m\) 的情况,会有 \(\sqrt m\) 个取值;
  • 对于 \(i\ge \sqrt m\) 的情况,几乎可以取遍 \([1,\sqrt m]\) 的值;

  不要忽略这个 \(2\) 的常数,我因为这个常数被卡了一晚上......

  我们做完一个点之后,整棵树就被分成了很多个部分,每个部分单独做,每次选择一个点做的复杂度是 \(\mathcal O(siz\sqrt m)\) 的,其中这个 \(siz\) 表示当前子树的大小,每次选择当前树的重心时,\(siz\) 以除以二的速度减少,最多只会有 \(\log n\) 次,其实就是点分树。我们用点分树做这个过程,复杂度就是 \(\mathcal O(n\sqrt m\log n)\).

叁、参考代码 ¶

/** @author Arextre; その可憐な少女は魔女であり、旅人でした。 ―― そう、私です。 */

#include <bits/stdc++.h>
using namespace std;

#define USING_FREAD
// #define NDEBUG
// #define NCHECK
#include <cassert>

namespace Elaina {

#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;

template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void getmin(T& x, const T rhs) { x = min(x, rhs); }
template<class T> inline void getmax(T& x, const T rhs) { x = max(x, rhs); }

#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 18
    static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
    return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif

template<class T> inline T readret(T x) {
    x = 0; int f = 0; char c;
    while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
    for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
    return f ? -x : x;
}
template<class T> inline void readin(T& x) {
    x = 0; int f = 0; char c;
    while ((c = CHARRECEI) < '0' || '9' < c) if (c == '-') f = 1;
    for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
    if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
    readin(x), readin(args...);
}
// default enter
template<class T> inline void writln(T x, char s = '\n') {
    static int fwri_sta[55], fwri_ed = 0;
    if (x < 0) putchar('-'), x = -x;
    do fwri_sta[++fwri_ed] = x % 10, x /= 10; while (x);
    while (putchar(fwri_sta[fwri_ed--] ^ 48), fwri_ed);
    putchar(s);
}

} // namespace Elaina
using namespace Elaina;

const int maxn = 2000;
const int sqm = 2000;
const int maxm = 1e6;
const int mod = 1e9 + 7;
inline void Add(int& x, const int& rhs) { (x += rhs) >= mod? x -= mod: 0; }

int n, m;
int val[maxn + 5];
vector<int> g[maxn + 5];
inline void add_edge(int u, int v) { g[u].push_back(v), g[v].push_back(u); }

inline void input() {
    readin(n, m); int u, v;
    rep (i, 1, n) readin(val[i]), g[i].clear();
    rep (i, 2, n) readin(u, v), add_edge(u, v);
}

int num[maxm + 5], refe[maxm + 5], cnt;
inline void prelude() {
    int las = cnt = 0;
    drep (i, m, 1) las = num[refe[m / i] = (m / i != las? ++cnt: cnt)] = m / i;
}

int dp[maxn + 5][sqm + 5], ans;
namespace DS {

int max_subtre_siz[maxn + 5], siz[maxn + 5];
bool used[maxn + 5];

inline void init() { memset(used, false, sizeof used); }

void findrt(int u, int par, int n, int& rt) {
    max_subtre_siz[u] = 0, siz[u] = 1;
    for (int v: g[u]) if (!used[v] && v != par) {
        findrt(v, u, n, rt), siz[u] += siz[v];
        getmax(max_subtre_siz[u], siz[v]);
    }
    getmax(max_subtre_siz[u], n - siz[u]);
    if (max_subtre_siz[u] < max_subtre_siz[rt]) rt = u;
}

void dfs(int u, int par) {
    memset(dp[u], 0, cnt + 1 << 2), siz[u] = 1;
    rep (i, 1, cnt) if (num[i] >= val[u])
        Add(dp[u][refe[num[i] / val[u]]], dp[par][i]);
    for (int v: g[u]) if (v != par && !used[v]) {
        dfs(v, u), siz[u] += siz[v];
        rep (j, 1, cnt) Add(dp[u][j], dp[v][j]);
    }
}
inline void solve(int rt) {
    memset(dp[0], 0, cnt + 1 << 2);
    dp[0][cnt] = 1;
    dfs(rt, 0);
    for (int j = 1; j <= cnt; ++j)
        Add(ans, dp[rt][j]);
}

void build(int, int);
void divide(int rt) {
    used[rt] = true; solve(rt);
    for (int v: g[rt]) if (!used[v]) build(v, siz[v]);
}
void build(int u, int n) {
    max_subtre_siz[0] = n; int rt = 0;
    findrt(u, 0, n, rt);
    divide(rt);
}

} // namespace DS

inline void work() {
    input();
    prelude();
    ans = 0;
    DS::init();
    DS::build(1, n);
    writln(ans);
}

signed main() {
    rep (_, 1, readret(1)) work();
    return 0;
}

肆、关键 の 地方 ¶

  实际上我们每次统计的都是包含当前重心的情况,然后以重心将树分成多个子状态。因此,使用点分树的前提,你需要先考察固定一个点必在情况应该如何处理。并且重心之间的情况应该相互独立,不能反复统计。

posted @ 2021-11-10 15:31  Arextre  阅读(75)  评论(0编辑  收藏  举报