1. 程式人生 > >[JLOI2016/SHOI2016]偵察守衛(樹形dp)

[JLOI2016/SHOI2016]偵察守衛(樹形dp)

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)