P3576 题解
我们来看一下这道题:
树上问题,容易想到树形 $dp$
设 $dp_{i}$ 表示指定边到第 $i$ 条边要分叉多少?
容易得到
$$dp_v = dp_u \times (num_u - 1)$$
其中 $num_i$ 为节点 $i$ 的度。
但这样在算结果时会超时。
有没有更好的方法呢?没有
显然,会被吃一定是有范围的,
那么不妨设 $dp_{i,0/1}$ 表示从 $i$ 节点走到指定边会被吃的上界 $/$ 下界,
即从 $i$ 出发 $p$ 个蚂蚁,
若 $dp_{i, 0} \leq p \leq dp_{i, 1}$,则到指定边时会有 $k$ 只蚂蚁,
若不在这个范围内,则不会被吃。
容易得到:
$$dp_{v, 0} = dp_{u, 0} \times (num_u - 1)$$
$$dp_{v, 1} = (dp_{v, 0} + 1) \times (num_u - 1) - 1 $$
既然已经得到了每个点的上下界,那出入口的上下界也得到了,废话 !
最终就可以二分查找得出结果
复杂度 $O (n log (g))$
我们可以把指定边 $(x, y)$ 拆分成 $(0, x)$ 与 $(0, y)$,$0$ 号节点当作树的根节点,且上文的指定边到 $i$ 号节点就是 $0$ 号节点到 $i$ 号节点。
具体请见代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define int long long // 要开 long long
const int maxn = 1e6 + 10;
struct node {
int to, nxt;
} edge[maxn >> 1];
int cnt, head[maxn]; // 链式前向星
int n, m, k, ans = 0, a[maxn], num[maxn], dp[maxn][2];
void add (int u, int v) {
cnt ++;
edge[cnt].to = v;
edge[cnt].nxt = head[u];
head[u] = cnt;
} // 加边
void add_edge (int u, int v) {
add (u, v); add (v, u);
num[u] ++; num[v] ++;
} // 别忘了更新 num
void dfs (int u, int fa) {
dp[u][0] = dp[fa][0] * (num[fa] - 1);
dp[u][1] = (dp[fa][1] + 1) * (num[fa] - 1) - 1;
for (int i = head[u]; i; i = edge[i].nxt) {
int v = edge[i].to;
if (v == fa) continue;
dfs (v, u);
}
} // 树形 dp
signed main() {
scanf ("%lld%lld%lld", &n, &m, &k);
for (int i = 1; i <= m; ++i) {
scanf ("%lld", &a[i]);
}
sort (a + 1, a + m + 1); // 不要忘记排序哦!
int x, y; scanf ("%lld%lld", &x, &y); x ++; y ++;
add_edge (1, x); add_edge (1, y);
for (int i = 2; i < n; ++i) {
int u, v; scanf ("%lld%lld", &u, &v); u ++; v ++;
add_edge (u, v);
}
dp[1][0] = dp[1][1] = k;
dfs (x, 1); dfs (y, 1);
for (int i = 2; i <= n + 1; ++i) {
if (num[i] == 1) {
ans += upper_bound (a + 1, a + m + 1, dp[i][1]) - lower_bound (a + 1, a + m + 1, dp[i][0]); // 上界查找与下界查找
}
}
printf ("%lld", ans * k); // 乘 k 别忘了
return 0;
}