显然dp
考虑每个节点需要考虑的问题:
第一:这个点下面被覆盖的情况
第二:这个点对上面的贡献
据此我们设计状态:
$f[i][j]$表示以$i$为根节点的子树中已经覆盖好了下面剩余层,只剩下上面$j$层还没覆盖
$g[i][j]$表示以$i$为根节点的子树中每个点都被覆盖了,而且还能向上覆盖$j$层
那么考虑转移:设根节点为$x$,其某一个子节点为$to$
首先对于$g$:$g[x][j]=min(g[x][j]+f[to][j],f[x][j+1]+g[to][j+1])$
这个转移的原理:先假设这个子节点需要被覆盖,那么根节点必须覆盖好之前的部分,然后用$j$的贡献去覆盖这个子节点下面的部分
然后假设这个子节点可以对上面产生贡献,那么之前的部分就不需要覆盖好,允许有相同的层数未被覆盖
接下来对于$f$:$f[x][j]=sum f[to][j-1]$
这个好理解,下面的向上转移少覆盖的多了一层就行了嘛
然后考虑互相之间的贡献,有:$f[x][j]=min(f[x][j],f[x][j-1])$,$g[x][j]=min(g[x][j],g[x][j+1])$即我们可以用更小的代价换取更好的效果
因此本题结束
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #include <vector> #define ll long long using namespace std; vector <int> v[500005]; int n,m,d; bool vis[500005]; ll w[500005]; ll f[500005][25],g[500005][25]; void dfs(int x,int fx) { if(vis[x])f[x][0]=g[x][0]=w[x]; for(int i=1;i<=d;i++)g[x][i]=w[x]; g[x][d+1]=f[x][d+1]=0x3f3f3f3f3f3f3f3fll; for(int i=0;i<v[x].size();i++) { int to=v[x][i]; if(to==fx)continue; dfs(to,x); } for(int i=0;i<v[x].size();i++) { int to=v[x][i]; if(to==fx)continue; for(int j=0;j<=d;j++)g[x][j]=min(g[x][j]+f[to][j],f[x][j+1]+g[to][j+1]); for(int j=d;j>=0;j--)g[x][j]=min(g[x][j],g[x][j+1]); f[x][0]=g[x][0]; for(int j=1;j<=d;j++)f[x][j]+=f[to][j-1]; for(int j=1;j<=d;j++)f[x][j]=min(f[x][j],f[x][j-1]); } } template <typename T>inline void read(T &x) { T f=1,c=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();} x=c*f; } int main() { read(n),read(d); for(int i=1;i<=n;i++)read(w[i]); read(m); for(int i=1;i<=m;i++) { int x;read(x); vis[x]=1; } for(int i=1;i<n;i++) { int x,y; read(x),read(y); v[x].push_back(y),v[y].push_back(x); } dfs(1,0); printf("%lld ",g[1][0]); return 0; }