P3780 [SDOI2017]蘋果樹
阿新 • • 發佈:2021-09-22
P3780 [SDOI2017]蘋果樹
題目大意:
給定一個有根樹,每個節點有權值 \(v_i\),節點值至多取 \(a_i\) 次,選兒子節點一定要同時選父親節點一次,取 \(k\) 個節點值。
除此之外,還可以取一條最長鏈,求最大權值。
思路:
拿到這道題,先轉化成理解的形式:樹上揹包 \(dp\) +一堆不知道什麼東西。
————————————————————————
此時需要一個知識點: 樹的後序遍歷:
其實就是求彈棧的順序,此時有兩個特殊性質可以利用:
- 子樹 \(dfs\) 序連續 ,與前序遍歷相同
- 任意一個點 \(i\) 的子樹在這個序列上都是一個區間,而且 \(i\) 是區間的右端點。
————————————————————————
最長鏈一定是從根節點到葉子的一條路徑。又因為點權是正的,肯定取越多越好。
對於所有點有一個隱藏的性質:
我如果要第二次取這個節點的值,首先要取一次值才行,這跟題目中要取兒子一定先取父親的邏輯關係是一樣的。
我們進行拆點:如果 \(a_i > 1\) ,\(i\) 拆成兩個點:第一個 \(a_i=1\) ,第二個點是 \(i\) 的兒子節點 \(i'\),\(a_{i'}=a_i-1\),且不和其他點連線。
這樣就可以直接列舉路徑,考慮 \(dp\) ,有 \(dp[i][j]\) 表示決策到了後序遍歷第 \(i\) 個點,選取 \(j\)
那麼我們列舉這個物品選還是不選,如果不選的話,它的子樹全部不可選,而不可選的子樹是一段區間,只能從 \(dp[i-size_i][j]\) 轉移過來
則有轉移方程:
\[dp[i][j]=max^{a_i}_{k=1}(dp[i-1][j-k],dp[i-size_i][j]) \]我們後續跑一遍 \(dp\) ,然後把鄰接表反轉一遍再跑一遍,此時 \(dp[i][k]\) 就是再後序遍歷前 \(i\) 個點中選擇 \(k\) 個物品的最大收益的答案。
玄學卡常:
- 運用單向邊儲存
- 利用一維陣列模擬二維,減少訪問,節約時間。
- 不要寫雙端佇列,儘量手寫
// P3780 [SDOI2017]蘋果樹 #include<bits/stdc++.h> const int N=4e4+10,K=5e5+10,NK=6e7+10; using namespace std; inline void clear(vector <int>& ve){vector<int>emp; swap(emp,ve);} vector<int> v[N]; int w[N],a[N],fa[N],dfn1[N],dfn2[N],df1,df2,sizes[N],line[N]; int dp1[NK],dp2[NK],nfd1[N],nfd2[N],q1[K<<1],q2[K<<1]; bool lf[N]; int T,n,k,res,cnt,h,head=1,tail=0; void init(){ for(int i=0;i<=cnt;i++) clear(v[i]),lf[i]=line[i]=sizes[i]=0; for(int i=0;i<=(cnt+1)*(k+1);i++) dp1[i]=dp2[i]=0; df1=df2=res=cnt=h=0; } void dypr(int *dfn,int *dp){//dp過程,單調佇列進行手寫優化 for(int i=1;i<=cnt;i++){ int v=dfn[i];head=tail=1; q1[1]=q2[1]=0; for(int j=1;j<=k;j++){ if(q1[head]<j-a[v]) head++;//剩餘更多,更加優秀 int val=dp[(i-1)*(k+1)+j]-j*w[v]; //這裡是運用了二維轉一維的寫法 dp[i*(k+1)+j]=max(q2[head]+j*w[v],dp[(i-sizes[v])*(k+1)+j]);//單調佇列優化轉移 while(head<=tail&&q2[tail]<=val) tail--; q1[++tail]=j; q2[tail]=val; } } } void dfs1(int x){ sizes[x]=1; for(int i=0;i<v[x].size();i++){ int y=v[x][i]; dfs1(y); sizes[x]+=sizes[y]; } dfn1[++df1]=x; nfd1[x]=df1;//後序遍歷,記錄入點 } void dfs2(int x){//逆鄰接表 for(int i=v[x].size()-1;i>=0;i--){ int y=v[x][i]; line[y]=line[x]+w[y]; dfs2(y); } dfn2[++df2]=x; nfd2[x]=df2; } int main(){ cin>>T; while(T--){ scanf("%d%d",&n,&k); cnt=n; for(int i=1;i<=n;i++) scanf("%d%d%d",&fa[i],&a[i],&w[i]),lf[fa[i]]=true; for(int i=1;i<=n;i++){//拆點,加邊 v[fa[i]].push_back(i); if(a[i]>1){ a[++cnt]=a[i]-1; a[i]=1; w[cnt]=w[i]; v[i].push_back(cnt); } } line[1]=w[1]; dfs1(1); dfs2(1); dypr(dfn1,dp1); dypr(dfn2,dp2); for(int i=1;i<=n;i++){ if(lf[i]) continue; for(int j=0;j<=k;j++){ res=max(res,dp1[(nfd1[i]-1)*(k+1)+j]+line[i]+dp2[(nfd2[i]-sizes[i])*(k+1)+(k-j)]); res=max(res,dp1[(nfd1[i]-1)*(k+1)+j]+line[i]+dp2[(nfd2[i]-sizes[i])*(k+1)+(k-j)]); } } printf("%d\n",res); init(); } system("pause"); return 0; }