【洛谷P2305】購票
阿新 • • 發佈:2021-10-03
題目
題目連結:https://www.luogu.com.cn/problem/P2305
今年夏天,NOI 在 SZ 市迎來了她三十週歲的生日。來自全國 \(n\) 個城市的 OIer 們都會從各地出發,到 SZ 市參加這次盛會。
全國的城市構成了一棵以 SZ 市為根的有根樹,每個城市與它的父親用道路連線。為了方便起見,我們將全國的 \(n\) 個城市用 \(1\sim n\) 的整數編號。其中 SZ 市的編號為 \(1\)。對於除 SZ 市之外的任意一個城市 \(v\),我們給出了它在這棵樹上的父親城市 \(f_v\) 以及到父親城市道路的長度 \(s_v\)。
從城市 \(v\) 前往 SZ 市的方法為:選擇城市 \(v\)
對於任意一個城市 \(v\),我們會給出一個交通工具的距離限制 \(l_v\)。對於城市 \(v\) 的祖先 A,只有當它們之間所有道路的總長度不超過 \(l_v\) 時,從城市 \(v\) 才可以通過一次購票到達城市 A,否則不能通過一次購票到達。
對於每個城市 \(v\),我們還會給出兩個非負整數 \(p_v,q_v\) 作為票價引數。若城市 \(v\) 到城市 A 所有道路的總長度為 \(d\),那麼從城市 \(v\)
每個城市的 OIer 都希望自己到達 SZ 市時,用於購票的總資金最少。你的任務就是,告訴每個城市的 OIer 他們所花的最少資金是多少。
\(n\leq 2\times 10^5\),\(0\leq p_i\leq 10^6\)。
思路
很顯然有一個 dp:設 \(f[x]\) 表示到達 \(x\) 的最小代價。轉移
\[f[x]=\min(f[y]+(dis_x-dis_y)p_x+q_x) \]其中 \(y\) 是 \(x\) 的祖先並且 \(dis_x-dis_y\leq l_x\)。
這個東西看上去可以斜率優化,但是因為有祖先以及距離的限制並不是很好直接搞。
先 dfs 一次求出所有點的出棧順序,然後再 dfs 一次按照 dfs 序考慮轉移。對於一個點 \(x\)
因為斜率 \(p_i\leq 10^6\),用線段樹套動態開點李超樹就可以了。
時間複雜度 \(O(n\log^2 n)\),空間複雜度 \(O(n\log n)\)。因為動態開點李超樹一次修改只會增加 \(O(1)\) 個節點。
程式碼
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010,M=1000010,LG=19;
int n,tot,typ,head[N],id[N],fa[N][LG+1];
ll p[N],q[N],l[N],s[N],f[N];
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
void dfs1(int x)
{
for (int i=1;i<=LG;i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for (int i=head[x];~i;i=e[i].next)
s[e[i].to]+=s[x],dfs1(e[i].to);
id[x]=++tot;
}
int jump(int x,ll d)
{
for (int i=LG;i>=0;i--)
if (s[x]-s[fa[x][i]]<=d)
d-=s[x]-s[fa[x][i]],x=fa[x][i];
return x;
}
ll calc(int x,ll k)
{
return -s[x]*k+f[x];
}
struct SegTree2
{
int tot,lc[N<<5],rc[N<<5],res[N<<5];
int update(int x,int l,int r,int v)
{
if (!x) { res[++tot]=v; return tot; }
if (l==r)
{
if (calc(v,l)<calc(res[x],l)) res[x]=v;
return x;
}
int mid=(l+r)>>1;
if (calc(v,l)<calc(res[x],l))
{
if (calc(v,mid)<calc(res[x],mid))
rc[x]=update(rc[x],mid+1,r,res[x]),res[x]=v;
else
lc[x]=update(lc[x],l,mid,v);
}
else
{
if (calc(v,mid)<calc(res[x],mid))
lc[x]=update(lc[x],l,mid,res[x]),res[x]=v;
else
rc[x]=update(rc[x],mid+1,r,v);
}
return x;
}
ll query(int x,int l,int r,int k)
{
if (!x) return 1e18;
if (l==r) return calc(res[x],k);
int mid=(l+r)>>1;
if (k<=mid) return min(query(lc[x],l,mid,k),calc(res[x],k));
else return min(query(rc[x],mid+1,r,k),calc(res[x],k));
}
}seg2;
struct SegTree1
{
int rt[N*4];
void update(int x,int l,int r,int k,int v)
{
rt[x]=seg2.update(rt[x],0,1e6,v);
if (l==r) return;
int mid=(l+r)>>1;
if (k<=mid) update(x*2,l,mid,k,v);
else update(x*2+1,mid+1,r,k,v);
}
ll query(int x,int l,int r,int ql,int qr,ll v)
{
if (ql<=l && qr>=r) return seg2.query(rt[x],0,1e6,v);
int mid=(l+r)>>1; ll res=1e18;
if (ql<=mid) res=min(res,query(x*2,l,mid,ql,qr,v));
if (qr>mid) res=min(res,query(x*2+1,mid+1,r,ql,qr,v));
return res;
}
}seg1;
void dfs2(int x)
{
int y=jump(x,l[x]);
if (x>1)
f[x]=seg1.query(1,1,n,id[x],id[y],p[x])+s[x]*p[x]+q[x];
seg1.update(1,1,n,id[x],x);
for (int i=head[x];~i;i=e[i].next)
dfs2(e[i].to);
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&typ);
for (int i=2,x;i<=n;i++)
{
scanf("%d%lld%lld%lld%lld",&x,&s[i],&p[i],&q[i],&l[i]);
add(x,i); fa[i][0]=x;
}
tot=0; s[0]=-1e18;
dfs1(1); dfs2(1);
for (int i=2;i<=n;i++)
cout<<f[i]<<"\n";
return 0;
}