[BZOJ4557/LOJ2024/Luogu3267][GZOI2016/JLOI2016/SHOI2016]侦察守卫
题目链接:
#2024.「JLOI/SHOI2016」侦查守卫-Libre OJ
P3267[JLOI2016/SHOI2016]侦察守卫-Luogu
首先对于这数据范围。。一看就知道是个\(O(nd)\)的树形\(DP\)了。
那么是\(O(n)\)的状态\(O(d)\)转移,还是\(O(nd)\)状态数\(O(1)\)转移呢?
如果是前者,不说\(O(d)\),直接没法转移啊\(qwq\)
那么就只能选另一种方法了。
设\(f[i][j]\)表示在以\(i\)为根的子树中有\(j\)层没有监视(包括\(i\))时的最小代价。
设\(g[i][j]\)表示在以\(i\)为根的子树中监视完,还能向上监视\(j\)层(包括\(i\))的最小代价。
那么转移方程就很好写了,详细见代码。
代码:
#include <cstdio>
#include <cctype>
inline int Min(const int a,const int b){return a<b?a:b;}
inline int Max(const int a,const int b){return a>b?a:b;}
inline int Getint()
{
register int x=0,c;
while(!isdigit(c=getchar()));
for(;isdigit(c);c=getchar())x=x*10+(c^48);
return x;
}
int n,d,w[500005],m;
int Head[500005],Next[1000005],To[1000005],En;
int f[500005][22],g[500005][22];
bool Vis[500005];
inline void Add(const int x,const int y)
{
Next[++En]=Head[x];
To[Head[x]=En]=y;
}
void DP(int x,int Pre)
{
if(Vis[x])f[x][0]=g[x][0]=w[x];
//有"B"神,那么当只监视这层时必须放守卫。
for(int i=1;i<=d;++i)g[x][i]=w[x];
//在这里放是其中一种方案
g[x][d+1]=0x3f3f3f3f;
//避免不合法情况
for(int i=Head[x],y;i;i=Next[i])
if((y=To[i])!=Pre)
{
DP(y,x);//遍历子树
for(int j=d;j>=0;--j)
{
g[x][j]=Min(g[x][j]+f[y][j],g[y][j+1]+f[x][j+1]);
g[x][j]=Min(g[x][j],g[x][j+1]);
}
//将y和之前的子树结果合并,或者将x,y交换后合并
//第一种决策(g[x][j]+f[y][j]):
//因为g[x][j]已经能向上监视j层,向下也可以(守卫在子树内)
//那么子树y中可以有j层不用管
//第二种决策(g[y][j+1]+f[x][j+1])同理,注意要倒序处理,防止覆盖(类似背包?)
//至于最后一个(g[x][j+1]),如果范围更大还便宜,岂不美哉?
f[x][0]=g[x][0];//如果j=0,那么f和g意义相同。
for(int j=1;j<=d+1;++j)
f[x][j]=Min(f[x][j]+f[y][j-1],f[x][j-1]);
//第一种是直接合并,那么另一个就是取最优了。
}
}
int main()
{
n=Getint(),d=Getint();
for(int i=1;i<=n;++i)w[i]=Getint();
m=Getint();
for(int i=1;i<=m;++i)Vis[Getint()]=true;
for(int x,y,i=1;i<n;++i)
{
x=Getint(),y=Getint();
Add(x,y),Add(y,x);
}
DP(1,0);
printf("%d\n",f[1][0]);
return 0;
}