1. 程式人生 > 其它 >【洛谷P2305】購票

【洛谷P2305】購票

題目

題目連結: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\)

的一個祖先 \(a\),支付購票的費用,乘坐交通工具到達 \(a\)。再選擇城市 \(a\) 的一個祖先 \(b\),支付費用併到達 \(b\)。以此類推,直至到達 SZ 市。
對於任意一個城市 \(v\),我們會給出一個交通工具的距離限制 \(l_v\)。對於城市 \(v\) 的祖先 A,只有當它們之間所有道路的總長度不超過 \(l_v\) 時,從城市 \(v\) 才可以通過一次購票到達城市 A,否則不能通過一次購票到達。
對於每個城市 \(v\),我們還會給出兩個非負整數 \(p_v,q_v\) 作為票價引數。若城市 \(v\) 到城市 A 所有道路的總長度為 \(d\),那麼從城市 \(v\)
到城市 A 購買的票價為 \(dp_v+q_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\)

,可以通過倍增求出他深度最小的且滿足 \(dis_x-dis_y\leq l_x\) 的祖先 \(y\),那麼此時在出棧順序的序列中,\(x\)\(y\) 之間的所有元素,要麼是 \(x\)\(y\) 鏈上的點,要麼是第二次 dfs 還沒有訪問過的點。那麼等於需要支援區間詢問當斜率為 \(k\) 時,區間內點的最小截距。
因為斜率 \(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;
}