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]);
}
评论 (0)