题解 CF1111E 【Tree】
GKxx
2019-04-21 14:44:05
比赛的时候满脑子虚树 就死掉了
看来真的不能先入为主
这种询问点集的,首先考虑建虚树,然后按套路设$dp(x,i)$表示将以$x$为根的子树中的关键点分成$i$组的方案数,那么你很快就会发现这玩意根本不能转移,因为它不是正常的背包。
换一个思路,如果我们考虑将关键点按$\text{dfs}$序排序,那么对于一个点$\text{node}_i$,所有不能跟它放在同一组里的点(也就是它到根的路径上的点)都会排在它前面。不妨设点$x$到根的路径上的关键点为$f(x)$,设$dp(i,j)$表示前$i$个点分成$j$组的方案数,那么就有一个类似于第二类斯特林数的转移:
$$dp(i,j)=\max\{j-f(\text{node}_i),0\}\cdot dp(i-1,j)+dp(i-1,j-1)$$
$f(x)$的处理非常简单:先把每个点在树上做个标记,然后查询$x$到根$r$的路径上有多少个标记就行了(不要忘了减去自己),树剖+树状数组即可解决。
处理完询问不要忘了清空
```cpp
#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
template <typename T> inline void read(T& x) {
int f = 0, c = getchar(); x = 0;
while (!isdigit(c)) f |= c == '-', c = getchar();
while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
read(x); read(args...);
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) { write(x); puts(""); }
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, true) : false; }
const int maxn = 1e5 + 207;
const int maxm = 307;
const int mod = 1e9 + 7;
int v[maxn << 1], head[maxn], next[maxn << 1], tot;
int dep[maxn], size[maxn], top[maxn], son[maxn], fa[maxn], dfn[maxn], xys;
int n, q;
inline void ae(int x, int y) {
v[++tot] = y; next[tot] = head[x]; head[x] = tot;
v[++tot] = x; next[tot] = head[y]; head[y] = tot;
}
void dfs(int x) {
size[x] = 1; dep[x] = dep[fa[x]] + 1;
for (int i = head[x]; i; i = next[i])
if (v[i] != fa[x]) {
fa[v[i]] = x;
dfs(v[i]);
size[x] += size[v[i]];
if (size[v[i]] > size[son[x]]) son[x] = v[i];
}
}
void dfs(int x, int t) {
top[x] = t; dfn[x] = ++xys;
if (son[x]) dfs(son[x], t);
for (int i = head[x]; i; i = next[i])
if (v[i] != son[x] && v[i] != fa[x])
dfs(v[i], v[i]);
}
int s[maxn];
inline void add(int x, int val) { for (; x <= n; x += x & -x) s[x] += val; }
inline int sum(int l, int r) {
int ans = 0;
for (; r; r -= r & -r) ans += s[r];
for (--l; l; l -= l & -l) ans -= s[l];
return ans;
}
inline void mark(int x) { add(dfn[x], 1); }
inline void del(int x) { add(dfn[x], -1); }
inline int query(int x, int y) {
int ans = 0;
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) std::swap(x, y);
ans += sum(dfn[top[x]], dfn[x]);
x = fa[top[x]];
}
return ans + (dep[x] < dep[y] ? sum(dfn[x], dfn[y]) : sum(dfn[y], dfn[x]));
}
int dp[maxn][maxm];
int f[maxn];
int node[maxn], K, m, r;
inline void clear() {
for (int i = 1; i <= K; ++i)
for (int j = 1, _end = std::min(i, m); j <= _end; ++j)
dp[i][j] = 0;
for (int i = 1; i <= K; ++i) {
f[node[i]] = 0;
del(node[i]);
}
}
int main() {
read(n, q);
for (int i = 1, x, y; i < n; ++i)
read(x, y), ae(x, y);
dfs(1); dfs(1, 1);
while (q--) {
read(K, m, r);
for (int i = 1; i <= K; ++i) {
read(node[i]);
mark(node[i]);
}
for (int i = 1; i <= K; ++i)
f[node[i]] = query(node[i], r) - 1;
std::sort(node + 1, node + K + 1, [](int a, int b) -> bool { return f[a] < f[b]; });
dp[1][1] = 1;
for (int i = 2; i <= K; ++i)
for (int j = 1, _end = std::min(i, m); j <= _end; ++j)
if (j - f[node[i]] >= 0)
dp[i][j] = (1ll * dp[i - 1][j] * (j - f[node[i]]) % mod + dp[i - 1][j - 1]) % mod;
else
dp[i][j] = dp[i - 1][j - 1];
int ans = 0;
for (int i = 1; i <= m; ++i)
if ((ans += dp[K][i]) >= mod) ans -= mod;
writeln(ans);
clear();
}
return 0;
}
```