[HDU6643]Ridiculous Netizens
壹、题目描述 ¶
贰、题解 ¶
如果没有 \(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;
}
肆、关键 の 地方 ¶
实际上我们每次统计的都是包含当前重心的情况,然后以重心将树分成多个子状态。因此,使用点分树的前提,你需要先考察固定一个点必在情况应该如何处理。并且重心之间的情况应该相互独立,不能反复统计。