Loading

P2305 [NOI2014] 购票 - 斜率优化

题意

给定边带权的有根树,根是 \(1\)。对于每个点 \(u>1\),给定参数 \(p_u,q_u,l_u\),求 \(\{f_n\}\) 满足:

\[f_1=0\\ f_u=\min_{v\in \mathrm{anc}_u \land \operatorname{dist}(u,v)\le l_u} \{ f_v+\operatorname{dist}(u,v)\cdot p_u\}+q_u \]

\(1\le n\le 2\times 10^5,\operatorname{dist}(1,u)\le 2\times 10^{11}\)。保证边权 \(>0\),答案 \(<2^{63}\)

题解

这里讲的是出栈序的做法。点分治以及可撤销栈的做法以后再补吧。

\(d_u=\operatorname{dist}(1,u)\)。我们将转移方程化为:

\[f_u=\min_{v\in \mathrm{anc}_u \land d_v\ge d_u-l_u} \{ f_v-d_vp_u\}+q_u+d_up_u \]

我们 dfs 整棵树,并记录每个点 \(u\) 的出栈时间 \(o_u\)。可以发现,假如再按照 dfs 的顺序计算每个点的 \(f\),那么对于一个点 \(u\) 和它的一个祖先 \(v\)\([o_u,o_v]\) 中计算过的有且仅有 \(v\to u\) 的链上的点。

当我们要计算 \(f_u\) 时,因为边权 \(>0\),所以我们可以找到深度最浅的一个祖先 \(v\) 满足 \(d_v\ge d_u-l_u\)。原问题是 \(v\to u\) 的链上的 \(\min\),我们可以转化成 \([o_u,o_v]\) 这个区间中的 \(\min\)

再考虑区间怎么做。这就相当于要支持:

  • 在序列上某个位置放上一个点;
  • 询问一个区间内的点形成的下凸壳。

因为边权 \(>0\),所以每次都相当于在凸壳中 push_back(而不是 insert),因此可以用线段树套单调栈维护,将区间查询拆成 \(\log\) 个线段树上的点即可。注意在凸壳上二分的写法。

复杂度 \(\mathcal{O}(n\log^2 n)\)

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define For(Ti,Ta,Tb) for(int Ti=(Ta);Ti<=(Tb);++Ti)
#define Dec(Ti,Ta,Tb) for(int Ti=(Ta);Ti>=(Tb);--Ti)
#define Debug(...) fprintf(stderr,__VA_ARGS__)
typedef long long ll;
typedef __int128 i128;
const int N=2e5+5;
const ll Inf=0x3f3f3f3f3f3f3f3f;
ll f[N],d[N];
bool Pop(int x,int y,int z){
	return i128(d[x]-d[y])*i128(f[y]-f[z])<i128(d[y]-d[z])*i128(f[x]-f[y]);
}
ll W(int x,ll k){return f[x]-d[x]*k;}
struct Stack{
	int *stk,top;
	Stack()=default;
	Stack(int len):stk(new int[len+1]),top(0){}
	void Push(int x){
		while(top>1&&Pop(stk[top-1],stk[top],x)) --top;
		stk[++top]=x;
	}
	int QMin(ll k){
		if(!top) return 0;
		if(top==1) return stk[top];
		int l=1,r=top;
		while(l<r){
			int mid=(l+r)>>1;
			if(W(stk[mid],k)>W(stk[mid+1],k)) l=mid+1;
			else r=mid;
		}
		return stk[l];
	}
};
struct SegmentTree{
	struct Node{
		int l,r;Stack stk;
	}t[N<<2];
	void Build(int p,int l,int r){
		t[p].l=l,t[p].r=r,t[p].stk=Stack(r-l+1);
		if(l==r) return;
		int mid=(l+r)/2;
		Build(p*2,l,mid),Build(p*2+1,mid+1,r);
	}
	void Insert(int p,int pos,int k){
		t[p].stk.Push(k);
		if(t[p].l==t[p].r) return;
		int mid=(t[p].l+t[p].r)>>1;
		if(pos<=mid) Insert(p*2,pos,k);
		else Insert(p*2+1,pos,k);
	}
	ll QMin(int p,int l,int r,ll k){
		if(l>t[p].r||r<t[p].l) return Inf;
		if(l<=t[p].l&&t[p].r<=r){
			int res=t[p].stk.QMin(k);
			return res?W(res,k):Inf;
		}
		return min(QMin(p*2,l,r,k),QMin(p*2+1,l,r,k));
	}
}seg;
int n,t,fa[N],dfn[N],dfx;
ll s[N],p[N],q[N],l[N];
vector<int> e[N];
void Dfs1(int u){
	d[u]=d[fa[u]]+s[u];
	for(int v:e[u]){
		Dfs1(v);
	}
	dfn[u]=++dfx;
}
vector<ll> psd;
vector<int> anc;
void Dfs2(int u){
	if(u!=1){
		auto it=lower_bound(psd.begin(),psd.end(),d[u]-l[u]);
		int v=*(anc.begin()+(it-psd.begin()));
		f[u]=seg.QMin(1,dfn[u],dfn[v],p[u])+q[u]+d[u]*p[u];
	}
	seg.Insert(1,dfn[u],u);
	psd.push_back(d[u]),anc.push_back(u);
	for(int v:e[u]){
		Dfs2(v);
	}
	psd.pop_back(),anc.pop_back();
}
int main(){
	#ifndef zyz
	ios::sync_with_stdio(false),cin.tie(nullptr);
	#endif
	cin>>n>>t;
	For(i,2,n){
		cin>>fa[i]>>s[i]>>p[i]>>q[i]>>l[i];
		e[fa[i]].push_back(i);
	}
	seg.Build(1,1,n);
	Dfs1(1);
	Dfs2(1);
	For(i,2,n) cout<<f[i]<<'\n';
	return 0;
}
posted @ 2022-01-16 15:22  Alan_Zhao_2007  阅读(72)  评论(0编辑  收藏  举报