题意简述:
给一个节点数为 (n) 且有 (n-1) 条边的连通图,给出每个点的点权 (c_i) ,对于每一个点,求出与其距离不大于 (m) 条路径的点的权值和
首先它是一棵树 (废话)
这题拿上手,不能正经搜索,考虑DP
设计状态 (f_{i,j}) 表示第 (i) 个点距离不大于 (j) 的点权和
那么 (f_{i,0}) 即为点权 (c_i) (初始化)
(k) 为点 (i) 经过一条边所到之处, (num_i) 表示 (k) 的个数,则转移方程为:
[f_{i,j} = sum f_{k,j-1} - (num_i-1) imes f_{i,j-2}
]
说明一下减去的部分,因为 (sum f_{k,j-1}) 肯定会重复算 (num_i) 次 (f_{i,j-2})
为什么,因为这个状态的定义就是相互的,我们只需要每次加上后一层的点权的总和,将前面的减去即可
大家可以画张图理解一下,其实就是简单的容斥原理。
特殊的,对于 (j = 1) 的情况,有转移方程:
[f_{i,j} = sum f_{k,j-1} + f_{i,j-1}
]
代码供出
#include<bits/stdc++.h>
using namespace std;
const int maxn = (int)1e5+7;
struct node { // 链式前向星存图
int nxt,dir;
}Edge[maxn<<1];
int head[maxn],num[maxn],cnt;
inline void Add(int u,int v) {
Edge[++cnt].dir = v,Edge[cnt].nxt = head[u],head[u] = cnt;
Edge[++cnt].dir = u,Edge[cnt].nxt = head[v],head[v] = cnt;
num[u]++,num[v]++;
}
int n,m,f[maxn][23]; // f[i][j] 表示第i个节点走j步时的总权值
int main() {
scanf("%d%d",&n,&m);
int u,v;
for(int i=1;i<n;++i) {
scanf("%d%d",&u,&v);
Add(u,v);
}
for(int i=1;i<=n;++i) scanf("%d",&f[i][0]); // 初始化
for(int i=1;i<=m;++i) // 进行DP
for(int j=1;j<=n;++j) {
for(int k=head[j];k;k=Edge[k].nxt)
f[j][i] += f[Edge[k].dir][i-1];
if(i>1) f[j][i] -= (num[j]-1) * f[j][i-2];
else f[j][i] += f[j][i-1];
}
for(int i=1;i<=n;++i) printf("%d
",f[i][m]);
return 0;
}