●BZOJ 3672 [Noi2014]購票
題鏈:
http://www.lydsy.com/JudgeOnline/problem.php?id=3672
題解:
斜率優化DP,點分治(樹上CDQ分治...)
這裏有一個沒有距離限制的簡單版:BZOJ 1767 [Ceoi2009]harbingers
定義$DP[i]$為從i出發到1號點的最小花費,$dis_i$為i到1號點的距離:
轉移:
$DP[i]=min(DP[j]+(dis_i-dis_j)P_j)+Q_i$
$\quad=min(DP[j]-dis_jP_i)+dis_iP_i+Q_i$
顯然這個$O(N^2)$的轉移會超時。
考慮優化:
假設對於當前計算的DP[i],有兩個轉移來源點:k,j,同時dis[k]<dis[j],設j點比k點優。
那麽有:$DP[j]-dis_j*P_i-(DP[k]-dis_k*P_i)<0$
則: $\frac{DP[j]-DP[k]}{dis[j]-dis[k]}<P[i]$
如果令 Slope(j,k)=$\frac{DP[j]-DP[k]}{dis[j]-dis[k]}$
那麽得到結論,如果 $dis_k<dis_j$,且Slope(j,k)<P[i]的話,則j點優於k點。
同時如果存在三個轉移來源點:k,j,i,滿足$dis_k<dis_j<dis_i$,
同時Slope(i,j)<Slope(j,k),則j點無效。
所以對於每個來源點二元組(dis[j],DP[j]),只需要在平面上維護一個下凸殼即可。
1).如果問題不在一顆樹上,而是在一個序列上,這個應該比較容易吧:一個裸的CDQ就可以解決了。
2).而如果問題沒有limit這個距離限制,即使在樹上,也比較容易了,因為隨著DFS遍歷樹時,dis是單增的,所以可以直接維護單調棧(詳細見BZOJ 1767 [Ceoi2009]harbingers)
但是現在既在樹上又有距離限制怎麽辦呢?這裏采用點分治來實現CDQ分治的功能,(可以叫做樹上CDQ分治麽)
對於當前的子樹,我們令根為u,(這個根是子樹內的點在前往1號點時都要經過的地方)。
並找到子樹內的重心cg(the center of gravity),
然後先遞歸處理cg為根時含有u的那顆子樹,
不難發現,cg的其它子樹的點(令這些點的集合為R)在前往1號點時,都會進過cg~u這一條鏈,
即這條鏈上的點可能會成為R裏的點的轉移點。
同時為了滿足距離這一限制,即每個點i最多只能向上到達 $dis_i-limit_i$這個位置
所以把R裏的點按 $dis_i-limit_i$排序後,從大到小枚舉R裏的i點,並把cg~u這條鏈上dis小於$dis_i-limit_i$的點用單調棧維護一個下凸殼。
接著在凸殼裏二分最優的轉移來源點即可。
整個過程是$O(Nlog_2^2N)$的。
(傷不起,讀入居然要long long!)
代碼:PoPoQQQ的代碼寫得很棒呀!
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 200050 #define ll long long using namespace std; ll DP[MAXN],P[MAXN],Q[MAXN],G[MAXN],dis[MAXN]; int fa[MAXN],siz[MAXN]; int N,t; struct Edge{ ll val[MAXN]; int to[MAXN],nxt[MAXN],head[MAXN],ban[MAXN],ent; Edge(){ent=2;} void Adde(int u,int v,ll w){ to[ent]=v; val[ent]=w; nxt[ent]=head[u]; head[u]=ent++; } }E; struct Mostk{ int s[MAXN],top; #define Slope(i,j) (1.0*(DP[i]-DP[j])/(dis[i]-dis[j])) void Reset(){top=0;} void Push(int i){ if(top&&dis[s[top]]==dis[i]) {if(DP[i]<DP[s[top]]) top--; else return;} while(top>1&&Slope(s[top-1],s[top])<Slope(s[top],i)) top--; s[++top]=i; } int Query(int i){//二分單調棧 static int l,r,mid,ret; if(!top) return 0; l=2; r=top; ret=1; while(l<=r){ mid=(l+r)>>1; if(Slope(s[mid-1],s[mid])>=P[i]) ret=mid,l=mid+1; else r=mid-1; } return s[ret]; } }S; void dfs(int u){ for(int i=E.head[u];i;i=E.nxt[i]){ int v=E.to[i]; dis[v]=dis[u]+E.val[i]; dfs(v); } } void getcg(int u,int &cg,int &num,int sum){ int maxnum=0; siz[u]=1; for(int i=E.head[u];i;i=E.nxt[i]){ int v=E.to[i]; if(E.ban[i]) continue; getcg(v,cg,num,sum); maxnum=max(maxnum,siz[v]); siz[u]+=siz[v]; } maxnum=max(maxnum,sum-siz[u]); if(maxnum<=num) num=maxnum,cg=u; } void trave(int u,int &cr,int *R){ R[++cr]=u; for(int i=E.head[u];i;i=E.nxt[i]){ if(E.ban[i]) continue; trave(E.to[i],cr,R); } } bool cmp(int i,int j){ return dis[i]-G[i]>dis[j]-G[j]; } void solve(int u,int num){ static int R[MAXN],cr,maxnum; if(num==1) return; int cg=u; maxnum=num; getcg(u,cg,maxnum,num); //-----------------------------準備遞歸u區域 for(int i=E.head[cg];i;i=E.nxt[i]) E.ban[i]=1; solve(u,num-siz[cg]+1); //-----------------------------開始解決當前層的貢獻 cr=0; S.Reset(); for(int i=E.head[cg];i;i=E.nxt[i]) trave(E.to[i],cr,R); sort(R+1,R+cr+1,cmp); for(int i=1,j=cg,k;i<=cr;i++){ while(j!=fa[u]&&dis[j]>=dis[R[i]]-G[R[i]]) S.Push(j),j=fa[j]; k=S.Query(R[i]); if(k) DP[R[i]]=min(DP[R[i]],DP[k]+(dis[R[i]]-dis[k])*P[R[i]]+Q[R[i]]); } //-----------------------------遞歸解決剩下子樹區域 for(int i=E.head[cg];i;i=E.nxt[i]) solve(E.to[i],siz[E.to[i]]); } void read(ll &x){ static int sn; static char ch; x=0; sn=1; ch=getchar(); while(ch<‘0‘||‘9‘<ch){if(ch==‘-‘)sn=-1;ch=getchar();} while(‘0‘<=ch&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();} x=x*sn; } int main(){ scanf("%d%d",&N,&t); memset(DP,0x3f,sizeof(DP)); DP[1]=0; ll f,s,p,q,g; for(int i=2;i<=N;i++){ read(f); read(s); read(p); read(q); read(g); fa[i]=f; P[i]=p; Q[i]=q; G[i]=g; E.Adde(f,i,s); } dfs(1); solve(1,N); for(int i=2;i<=N;i++) printf("%lld\n",DP[i]); return 0; }
●BZOJ 3672 [Noi2014]購票