Codeforces Round #530 (Div. 2) F 線段樹 + 樹形dp(自下往上)
阿新 • • 發佈:2019-05-03
標記 int define class mat tro spa 開始 i++
https://codeforces.com/contest/1099/problem/F
題意
一顆n個節點的樹上,每個點都有\(x[i]\)個餅幹,然後在i節點上吃一個餅幹的時間是\(t[i]\),有n-1條邊,每條邊有邊權w為經過一條邊所需時間,你從樹根開始先手向下走,然後對手割掉你所在節點到子節點的任意一條邊,你可以在任何時間選擇返回,在返回的過程中你可以選擇性吃掉經過節點的餅幹,問在雙方最優的情況下,你最多能在T時間之內吃掉多少餅幹並返回根節點(在足夠時間返回根節點的情況下吃掉盡可能多的餅幹)
題解
- 對於選擇哪個子節點對於雙方最優,只有到最後一層節點(葉子)才知道,所以需要從下往上解決問題
- 定義dp[u]為經過節點u並能返回根最多能吃多少餅幹,
- u為根,\(dp[u]=max(dp[v])\)
- u不為根,\(dp[u]=max2(dp[v])\),選擇第二大,因為最大被對手割掉
- u為葉子,dp[u]為剩下時間lt,所能吃掉的最多的餅幹數量
- dp[1]為答案
- u為根,\(dp[u]=max(dp[v])\)
- 權值線段樹(時間為x軸)維護路徑上能吃的餅幹數量num以及所需時間sum,因為到葉子的時候整條路徑的餅幹情況都標記在線段樹上,而一定是從時間小(貪心)的開始吃,所以可以很方便找到sum<=lt最大的num,線段樹起了一個類似標記數組的作用
代碼
#include<bits/stdc++.h> #define MAXN 1000005 #define m 1000000 #define ll long long #define mk make_pair #define ft first #define se second #define pii pair<int,int> using namespace std; vector<pii>G[MAXN]; ll sum[MAXN<<2],num[MAXN<<2],T; int dp[MAXN],t[MAXN],x[MAXN]; int n,u,w; void ud(int o,int l,int r,int p,int v){ sum[o]+=1ll*p*v;num[o]+=v; if(l==r)return ; int mid=(l+r)/2; if(p<=mid)ud(o<<1,l,mid,p,v); else ud(o<<1|1,mid+1,r,p,v); } ll qy(int o,int l,int r,ll lt){ if(sum[o]<=lt)return num[o]; if(l==r)return lt/l; int mid=(l+r)/2; if(lt>=sum[o<<1])return num[o<<1]+qy(o<<1|1,mid+1,r,lt-sum[o<<1]); return qy(o<<1,l,mid,lt); } void dfs(int u,ll lt){ if(lt<=0)return; ud(1,1,m,t[u],x[u]); dp[u]=qy(1,1,m,lt); int mx1=0,mx2=0; for(auto tp:G[u]){ int v=tp.ft,w=tp.se; dfs(v,lt-2*w); if(dp[v]>mx1){mx2=mx1;mx1=dp[v];} else if(dp[v]>mx2){mx2=dp[v];} } if(u==1)dp[u]=max(dp[u],mx1); else dp[u]=max(dp[u],mx2); ud(1,1,m,t[u],-x[u]); } int main(){ cin>>n>>T; for(int i=1;i<=n;i++)scanf("%d",&x[i]); for(int i=1;i<=n;i++)scanf("%d",&t[i]); for(int i=2;i<=n;i++){ scanf("%d%d",&u,&w); G[u].push_back(mk(i,w)); } dfs(1,T); cout<<dp[1]; }
Codeforces Round #530 (Div. 2) F 線段樹 + 樹形dp(自下往上)