[JLOI2016/SHOI2016]偵察守衛(樹形dp)
阿新 • • 發佈:2018-10-08
pre 放置 貪心 char 地圖 連通 註意 += stream
小R和B神正在玩一款遊戲。這款遊戲的地圖由N個點和N-1條無向邊組成,每條無向邊連接兩個點,且地圖是連通的。換句話說,遊戲的地圖是一棵有N個節點的樹。
遊戲中有一種道具叫做偵查守衛,當一名玩家在一個點上放置偵查守衛後,它可以監視這個點以及與這個點的距離在D以內的所有點。這裏兩個點之間的距離定義為它們在樹上的距離,也就是兩個點之間唯一的簡單路徑上所經過邊的條數。在一個點上放置偵查守衛需要付出一定的代價,在不同點放置守衛的代價可能不同。
現在小R知道了所有B神可能會出現的位置,請你計算監視所有這些位置的最小代價。
Solution
神題。
註意到d不是很大,所以可以設計一個NK的狀態:dp[i][j]表示i這個點為根的子樹已經處理完了,它還能在向上覆蓋j個點的最小代價。
但是還有可能會出現子樹之間相互覆蓋的情況,所以我們用f[i][j]表示以i為根的子樹向下還有j個點沒有覆蓋的最小代價。
轉移:
考慮dp數組如何轉移。
dp[i][j]<-min(dp[i][j]+dp[v][j](i剛好能夠向下覆蓋j個),dp[v][j+1]+f[u][j+1]);
相當於是我們把v合並到了當前子樹中。
f數組可以直接累加答案,f[i][j]+=f[v][j-1]。
最後結合一下貪心的思想,對於f數組,j越大答案應越小,對於dp數組,j越小答案也應越小,做完之後取一下max.
然後還要註意一下邊界,dp[u][0]=0.dp[u][~]=w[u].當vis[u]=1時f[u][0]=dp[u][0]=w[u];
Code
#include<iostream> #include<cstdio> #define N 500003 #define inf 0x3f3f3f3f using namespace std; int x,y,w[N],head[N],tot,f[N][22],d,dp[N][22],n,m,tag[N]; struct zzh{ int n,to; }e[N<<1]; inline void add(int u,int v){ e[++tot].n=head[u]; e[tot].to=v; head[u]=tot; }void dfs(int u,int fa){ if(tag[u])dp[u][0]=f[u][0]=w[u]; for(int i=1;i<=d;++i)dp[u][i]=w[u]; dp[u][d+1]=inf; for(int i=head[u];i;i=e[i].n)if(e[i].to!=fa){ int v=e[i].to; dfs(v,u); for(int j=d;j>=0;--j)dp[u][j]=min(dp[u][j]+f[v][j],dp[v][j+1]+f[u][j+1]); for(int j=d;j>=0;--j)dp[u][j]=min(dp[u][j],dp[u][j+1]); f[u][0]=dp[u][0]; for(int j=1;j<=d+1;++j)f[u][j]+=f[v][j-1]; for(int j=1;j<=d+1;++j)f[u][j]=min(f[u][j],f[u][j-1]); } } inline int rd(){ int x=0;bool f=0;char c=getchar(); while(!isdigit(c)){ if(c==‘-‘)f=1; c=getchar(); } while(isdigit(c)){ x=(x<<1)+(x<<3)+(c^48); c=getchar(); } return f?-x:x; } int main(){ n=rd();d=rd(); for(int i=1;i<=n;++i)w[i]=rd(); m=rd(); for(int i=1;i<=m;++i)x=rd(),tag[x]=1; for(int i=1;i<n;++i)x=rd(),y=rd(),add(x,y),add(y,x); dfs(1,0); cout<<f[1][0]; return 0; }
[JLOI2016/SHOI2016]偵察守衛(樹形dp)