Luogu P3047 [USACO12FEB]Nearby Cows G
题意简述:
给一个节点数为 \(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) \times 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\n",f[i][m]);
return 0;
}