NOI2014购票(树的cdq分治+凸壳)
题目链接:
http://uoj.ac/problem/7
思路:
如果没有距离限制L的话只要dfs一遍同时维护到根的凸壳就可以(二分位置后可以做到O(1)添加,O(1)恢复)。
这题有个距离限制的话就可以将重心下面的点按d[v]-l[v]排序后,与重心到根的点一起双指针更新。
#include<cstdio> #include<vector> #include<algorithm> using namespace std; typedef long long ll; const int N=200005; struct E { int v; ll z; }; vector<E>link[N]; ll d[N],p[N],q[N],l[N],f[N]; int fa[N]; struct P { ll d,f; ll operator*(const P &b)const { return d*b.d+f*b.f; } }stk[N]; int top; long double slope(P a,P b) { long double tmp=1.0; return tmp*(b.f-a.f)/(b.d-a.d); } void add(P p) { if (top<=1) { stk[top++]=p; return; } while(top>=2&&slope(p,stk[top-1])>slope(stk[top-1],stk[top-2])) top--; stk[top++]=p; } void upd(int id) { P vec={-p[id],1}; int l=0,r=top-1; while(l<r) { int mid=(l+r)/2; if (vec*stk[mid]>vec*stk[mid+1]) l=mid+1; else r=mid; } f[id]=min(f[id],p[id]*d[id]+q[id]+vec*stk[l]); } int cent,mxsz; int size[N]; int vis[N]; void findcent(int u,int n) { size[u]=1; int mx=0; for (E e:link[u]) { int v=e.v; if (vis[v]) continue; d[v]=d[u]+e.z; findcent(v,n); size[u]+=size[v]; mx=max(mx,size[v]); } mx=max(mx,n-size[u]); if (mx<=mxsz) cent=u,mxsz=mx; } int nd[N],tot; void add(int u) { nd[tot++]=u; for (E e:link[u]) if (!vis[e.v]) add(e.v); } bool cmp(int i,int j) { return d[i]-l[i]>d[j]-l[j]; } void solve(int u,int n) { if (n==1) return; cent=u; mxsz=n; findcent(u,n); int rt=cent; for (E e:link[rt]) vis[e.v]++; if (u==4&&n==2) u=4; solve(u,n-size[rt]+1); for (E e:link[rt]) vis[e.v]--; for (E e:link[rt]) add(e.v); sort(nd,nd+tot,cmp); int now=rt; for (int i=0;i<tot;i++) { int id=nd[i]; while(now!=fa[u]&&d[now]>=d[id]-l[id]) add({d[now],f[now]}),now=fa[now]; if (top) upd(id); } tot=0; top=0; for (E e:link[rt]) solve(e.v,size[e.v]); } int main() { int n,t; scanf("%d%d",&n,&t); for (int i=2;i<=n;i++) { f[i]=1ll<<62; ll s; scanf("%d%lld",&fa[i],&s); link[fa[i]].push_back({i,s}); scanf("%lld%lld%lld",&p[i],&q[i],&l[i]); } solve(1,n); for (int i=2;i<=n;i++) printf("%lld\n",f[i]); }