main
黄海 2 years ago
parent 1c5efff3e8
commit fea7c9ae58

@ -1,60 +1,77 @@
#include <bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
using namespace std;
#define int long long
#define endl "\n"
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
vector<int> edge[N];
int f[N][25], g[N][25];
int val[N];
int n, k;
const int N = 200010, M = N << 1, K = 21;
void dp(int u, int father) {
for (int i = 0; i <= k; i++)
f[u][i] = val[u];
for (int i = 0; i < edge[u].size(); i++) {
int son = edge[u][i];
if (son == father)
continue;
dp(son, u);
// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n, q;
int dp[N][K];
void dfs1(int u, int fa) {
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v == fa) continue;
dfs1(v, u);
for (int j = 1; j <= q; j++) dp[u][j] += dp[v][j - 1]; // 第一遍dp
for (int j = 1; j <= k; j++) {
f[u][j] += f[son][j - 1];
}
}
return;
}
void dfs2(int u, int fa) {
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v == fa) continue;
// 在第一次遍历时 dp[1][2] 包括了 dp[2][1] 2的子树权值
// 然鹅 ans在统计dp[2][3] 的时候也加上了 dp[2][1] 2的子树权值
// 第二次遍历 dp[2][3] 又加上了 dp[2][1];
// 所以需要简单容斥一下;
for (int j = q; j >= 2; j--)
dp[v][j] -= dp[v][j - 2]; // 简单容斥
for (int j = 1; j <= q; j++)
dp[v][j] += dp[u][j - 1]; // 第二遍dp
dfs2(v, u);
void dp2(int u, int father) {
for (int i = 0; i < edge[u].size(); i++) {
int son = edge[u][i];
if (son == father)
continue;
g[son][0] = val[son];
g[son][1] = f[son][1] + val[u];
for (int j = 2; j <= k; j++) {
g[son][j] = g[u][j - 1] + f[son][j] - f[son][j - 2];
}
dp2(son, u);
}
}
signed main() {
// 初始化链式前向星
memset(h, -1, sizeof h);
cin >> n >> q;
for (int i = 1; i < n; i++) {
void solve() {
cin >> n >> k;
for (int i = 0; i < n - 1; i++) {
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
edge[a].push_back(b);
edge[b].push_back(a);
}
for (int i = 1; i <= n; i++) cin >> dp[i][0]; // 每个节点往外0距离就是它本身的权值
dfs1(1, 0);
dfs2(1, 0);
for (int i = 1; i <= n; i++)
cin >> val[i];
dp(1, 0);
for (int i = 0; i <= k; i++) {
g[1][i] = f[1][i];
}
dp2(1, 0);
// for(int i = 1; i <= n; i ++ )
// {
// cout << f[i][k] << endl;
// }
// cout << endl;
for (int i = 1; i <= n; i++) {
int ans = 0;
for (int j = 0; j <= q; j++) ans += dp[i][j]; // ans统计答案
cout << ans << endl;
cout << g[i][k] << endl;
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
}

@ -689,24 +689,85 @@ int main() {
看数据范围就知道暴力肯定是会$TLE$飞的,所以我们要考虑如何$dp$(代码习惯写$dfs$
仔细思考一下我们发现点$i$走$k$步能到达的点分为以下两种
仔细思考一下我们发现点$i$走$k$步能到达的点分为以下两种:
- 在$i$的子树中(由$i$点往下)
- 经过$i$的父亲(由$i$点往上)
- 在$i$的子树中(由$i$点往下)
- 经过$i$的父亲(由$i$点往上)
这样的问题一般可以用两次$dfs$解决
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202401151124943.png)
这样的问题一般可以用两次$dfs$解决。
定义状态:
- $f[i][j]$表示$i$点往下$j$步范围内的点权之和
- $g[i][j]$表示$i$点往上和往下走$j$步范围内点权之和
- $g[i][j]$表示$i$点往上和往下走$j$步范围内点权之和:【答案在这里】
第一次$dfs$我们求出所有的$f[i][k]$,这个简单,对于节点$u$和其儿子$v$:
第一次$dfs$我们求出所有的$f[n][k]$,这个比较简单,对于节点$u$和其儿子$v$,$f[u][k] += f[v][j - 1]$就行了。(第一次$dfs$已知叶子节点推父亲节点)
**初始值**
$$f[i][0]=a[i]$$
> **解释**:每个节点,向下走$0$步,也就是一步不走,那还是它自己的点权,也就是$f[i][0]=a[i]$
第二次$dfs$我们通过已经求出的$f$数组推$g$数组,对于$u$和$u$的儿子$v$,
**递推式**
$$f[u][j] = \sum_{v \in son[u]}f[v][j - 1]$$
第二次$dfs$我们通过已经求出的$f$数组推$g$数组,对于$u$和$v$,
$$g[v][k] += (g[u][k - 1] - f[v][k - 2])$$
注意数组下表不要越界。$g[i][j]$的初始值应该赋为$f[i][j]$,因为根节点的$g[i][j]$就是$f[i][j]$。(第二次$dfs$已知父亲节点推儿子节点)
----
题目简单地来说就是:
给你一棵 $n$ 个点的树,点带权,对于每个节点求出距离它不超过 $k$ 的所有节点权值和 $m_i$。
对于树中的某个节点而言,距离它不超过$k$的节点主要来源于两方面:
- 一个是该节点的子节点中距离该节点不超过距离$k$的节点的权值和
- 一个是该节点向上沿着父节点方向不超过距离$k$的点的权值和
对于子节点方向的节点的权值和,我们可以先通过普通的树形$DP$计算出来。
因此,我们先写一个$DP$计算出子树中距离该点不超过$k$的点的权值和。
**1、状态表示**
$f[u][k]$表示以$u$为根节点的树中,距离$u$不超过$k$的子节点的权值和。
**2、状态转移**
$$f[u][j]=val[u]+\sum_{v \in son[u]}f[v][j1] \ j \in [1,k]$$
到节点$u$不超过距离$k$,即距离$son$不超过$k1$,然后加在一起即可。同时$u$节点本身也是答案,因为$u$节点本身是不超过距离$0$的节点。
**3、换根$DP$**
这个题目本身是个无根树,如果我们认为规定编号为$1$的节点是根的话,那么对于祖宗节点$1$来说,$f[1][k]$就是距离$1$节点不超过距离$k$的节点的权值和。因为祖宗节点是没有父亲节点的,所以我们就不需要考虑沿着父节点方向的节点权值和。
令:$g[u][k]$表示所有到$u$节点的不超过距离$k$的节点的权值和。根据刚刚的分析:$g[1][k]=f[1][k]$
这个就是我们换根$DP$的 **初始化**。其实受这个的启发,我们完全可以去把每个点都当作根,然后暴力跑出答案,但是这个暴力做法的时间复杂度是$O(n^2)$的,会超时。
所以当我们将祖宗节点从节点$1$换为另一个节点的时候,我们只能通过数学上的关系来计算出$g$数组元素的值。这个也是换根$DP$的意义。
我们看下面的图:
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202401151316634.png)
红色框是非常好理解的,直接写成$f[u][k]$即可。上面的部分,我们可以写成$g[fa(u)][k-1]$。
因为到$u$不超过$k$的距离,即距离它的父亲节点不超过$k1$的距离。
但是这么写对吗?
答案是不对的,$g[fa(u)][k-1]$和$f[u][k]$是有重复部分的。我们需要减去这段重复的部分,那么关键问题是重复部分如何表示?
重复部分肯定是出现在了红色框中,红色框中到$fa(u)$不超过距离$k1$,即距离$u$不超过$k-2$,同时重复部分又恰好是节点$u$的子节点,所以这部分可以表示为:$f[u][k-2]$。
所以最终的结果就是:
$$\large g[u][k]=f[u][k]+g[fa(u)][k1]f[u][k2]$$
但是上述方程成立的条件是$k\geq 2$的。
所以我们还得想一想$\leq 1$的时候。
如果$k=0$$g[u][0]$其实就是$val[u]$,因为不超过距离$0$的点只有本身。
如果$k=1$,那么$g[u][1]$其实就是$f[u][1]+val[fa(u)]$,因为沿着父节点方向距离$u$不超过$1$的点,只有父节点,而树中,父节点是唯一的。沿着子节点方向,其实就是$u$的各个子节点,而这些子节点可以统统用$f[u][1]f[u][1]$表示。
#### [$P6419$ $Kamp$](https://www.luogu.com.cn/problem/P6419)
https://www.cnblogs.com/Troverld/p/14601347.html

Loading…
Cancel
Save