P4657 [CEOI2017]Chase
阿新 • • 發佈:2022-04-22
不得不說這個樹形dp做法真牛
題目大意:給出一棵樹,求一條路徑,選擇路上的V個點,使得被選擇的點的相鄰且不在路徑上的點的權值和最大。
考慮換根的話很麻煩 還是類似毛毛蟲那個題 每個點一定是處於一個路徑的中間的 可能是一上一下 一下一上 一下 一上這四種情況
因為這個題目是有方向性的 a->b 的權值和 是不等於b->a 的 所以次大最大的方案也不是很好做 這個題解法的靈魂就是去重的操作
設b[x][i] 表示從x向x的子樹走取i個點的最大權值和
設c[x][i] 表示從x的子樹走到x取i個點的最大權值和
設g[x]表示x周圍的點的權值和
設f[x]表示x點的權值
轉移方程:
b[u][i]=max(b[u][i],b[v][tot-i]+g[u]-f[fa])
c[u][i]=max(c[u][i],c[v][tot-i]+g[u]-f[v])
答案:ans=max(ans,c[u][i]+b[v][tot-i],c[u][tot],b[u][tot])
設當前節點為x,y1,y2,y3...為x的兒子
為了避免重複走相同的路徑 我們在不斷在y跟新x之前就先統計答案
比如我們先跟新了y1和y2 此時在加入y3之前就跟新ans
我們用的是y1和y2跟新的c[][] 和y3的b[][]跟新的答案 所以c裡面一定不包含y3
這個題的解決辦法真的好優美
#include<cstdio> #include<string> #include<cstring> using namespace std; const int maxn=1e5+5; int N,V,x,y,tot,top,F[maxn],sta[maxn]; int son[maxn<<1],nxt[maxn<<1],lnk[maxn]; long long g[maxn],c[maxn][105],b[maxn][105],ans; inline int read() { int ret=0,f=1,ch=getchar(); for (; !isdigit(ch); ch=getchar()) if (ch=='-') f=-f; for (; isdigit(ch); ch=getchar()) ret=ret*10+ch-48; return ret*f; } inline void add_edge(int x,int y) { son[++tot]=y,nxt[tot]=lnk[x],lnk[x]=tot; son[++tot]=x,nxt[tot]=lnk[y],lnk[y]=tot; } inline void DP(int x,int y,int f) { // ans一定要在前面賦值,這樣可以避免從y走到x又走回y for (int i=1; i<=V; ++i) ans=max(ans,c[x][i]+b[y][V-i]); for (int i=1; i<=V; ++i) c[x][i]=max(c[x][i],max(c[y][i],c[y][i-1]+g[x]-F[y])), b[x][i]=max(b[x][i],max(b[y][i],b[y][i-1]+g[x]-F[f])); } void dfs(int x,int pre) { for (int i=1; i<=V; ++i) c[x][i]=g[x],b[x][i]=g[x]-F[pre]; for (int k=lnk[x]; k; k=nxt[k]) if (son[k]^pre) dfs(son[k],x),DP(x,son[k],pre); // 對於節點x,y和z是它的子節點並且,y先於z遍歷。 // 上面的方法,無法計算z->x->y所以要倒著做一遍 for (int i=1; i<=V; ++i) c[x][i]=g[x],b[x][i]=g[x]-F[pre]; top=0;for (int k=lnk[x]; k; k=nxt[k]) if (son[k]^pre) sta[++top]=son[k]; for (int i=top; i; --i) DP(x,sta[i],pre); ans=max(ans,max(c[x][V],b[x][V])); } int main() { N=read(),V=read(); for (int i=1; i<=N; ++i) F[i]=read(); for (int i=1; i<N; ++i) add_edge(x=read(),y=read()),g[x]+=F[y],g[y]+=F[x]; return dfs(1,0),printf("%lld",ans),0; }