2016年ACM/ICPC瀋陽賽區 I題(樹形dp+斜率優化)
阿新 • • 發佈:2018-11-11
題意:給你n個點,n-1條邊的樹。每條邊有一個權值w。給你一個值p。
1號節點為根節點。求1號點到所有節點的路徑中 的 最小權值 的最大值。
權值計算:相當於把這條路徑劃分成若干段,每一段的權值為這一段的所有邊的權值之和的平方。
每一段段尾如果不是目標城市,則需要支付p的費用。
思路(參考大佬部落格):容易看出來是一個樹形dp,並且有一個非常顯然的狀態轉移方程:
,其中v是樹上從u到根節點路徑上的點。
但是顯然這樣的時間複雜度在樹退化成鏈的時候會達到,需要想辦法來進行優化。嘗試進行變形:
如果狀態v和w都可以轉移到狀態u,那麼在這種情況下,從狀態v轉移會更優:
我們發現上式變成了一個斜率的形式。考慮將(dis[i], f[i])的點繪製出來,如果出現了下面的情況:
,那麼通過列舉各種情況,我們可以分析出來 j 處必不可能是較優的點。
也就是說,有可能作為最優解進行轉移的狀態,它們的點必然是在一個下凸殼上的。
每次在得到一個新的狀態的時候,由於dis[]的單調性,它的位置必然是在這個半凸殼的右端處。由於dis[]的單調性,f[]也是滿足單調遞增,這樣就可以用一個單調佇列來維護半凸殼上的點。
對於每個新的狀態u,具體的維護方法為:
1. 檢查隊頭的兩個元素q[l]和q[l+1],通過上面的斜率檢查,如果q[l+1]比q[l]更優,那麼就把q[l]出隊。
2. 直接取隊頭的元素為目標狀態,進行狀態轉移,計算出f[u]。
3. 將u插入隊尾。插入之前需要檢查三個狀態q[r-1], q[r], u是否滿足斜率單調遞增,若不滿足則將q[r]出隊。
這樣就將整個DP的時間複雜度優化到了。
需要注意的是,由於每個節點可能有多個子節點,因此每次轉移之後要將隊尾恢復為原來的元素。
程式碼:
#include<bits/stdc++.h> #define ll long long #define inf 0x3f3f3f3f3f3f3f3fLL using namespace std; const int maxn=200010; int n,m,k,x,y,s; ll ans,tmp,cnt,p,aa; ll zt[maxn],l,r; struct node { int to,nex; ll w; }a[maxn]; int he[maxn],tot,q[maxn]; ll dp[maxn],dis[maxn]; void add(int u,int v,ll w) { a[tot].to=v; a[tot].w=w; a[tot].nex=he[u]; he[u]=tot++; } void init() { tot=r=0;l=1; memset(he,-1,sizeof(he)); memset(dis,0,sizeof(dis)); ans=0;dp[1]=q[0]=0; } ll gety(int u,int v) { return dp[u]+dis[u]*dis[u]-dp[v]-dis[v]*dis[v]; } ll getx(int u,int v){return dis[u]-dis[v];} ll getdp(int u,int v){return dp[v]+p+(dis[u]-dis[v])*(dis[u]-dis[v]);} void getpre(int u,int fa) { for(int i=he[u];i!=-1;i=a[i].nex) { int v=a[i].to; if(v==fa) continue; dis[v]=dis[u]+a[i].w; // cout<<v<<" "<<dis[v]<<endl; getpre(v,u); } } void dfs(int u,int fa,int l,int r) { int pre=-1; while(l<r&&gety(q[l+1],q[l])<=2*dis[u]*getx(q[l+1],q[l])) l++; //cout<<dp[u]<<endl; dp[u]=min(dp[u],getdp(u,q[l])); while(l<r&&getx(u,q[r])*gety(q[r],q[r-1])>=gety(u,q[r])*getx(q[r],q[r-1])) r--; pre=q[++r];q[r]=u; ans=max(ans,dp[u]); for(int i=he[u];i!=-1;i=a[i].nex) { int v=a[i].to; if(v==fa) continue; dfs(v,u,l,r); } if(pre!=-1) q[r]=pre;//恢復隊尾 } int main() { int T,cas=1; scanf("%d",&T); while(T--) { scanf("%d%lld",&n,&p); init(); for(int i=0;i<n-1;i++) { scanf("%d%d%lld",&x,&y,&aa); add(x,y,aa); add(y,x,aa); } getpre(1,-1); for(int i=1;i<=n;i++) dp[i]=dis[i]*dis[i]; dfs(1,-1,1,0); printf("%lld\n",ans); // if(flag) puts("Yes"); else puts("No"); } return 0; }