1. 程式人生 > 其它 >P4657 [CEOI2017]Chase

P4657 [CEOI2017]Chase

不得不說這個樹形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;
}