P2305题解

P2305 [NOI2014] 购票

洛谷csdn题解共存,未经许可请勿转载!

luogu题解传送门

没事攻击一下自己的弱点: d p dp dp

斜率优化dp啊,你有点明显了

这一题值得注意的不是要用斜率优化,而是要在树上求答案

我一想,哎,麻烦,于是就把题目关了。。。。。。

这里给大家说一个简单一点的办法。

我们先设 G G G 为现在的重心,递归处理根 r o o t root root 所在的连通块,然后用从 G G G r o o t root root 路径上 f f f 不断更新 G G G 下边的点。

按这些点可以到达的最浅祖先的深度排序,依次加入新点,斜率优化维护,二分凸包求解即可。

时间复杂度稳定保持 O ( n l o g 2 n ) O(n log2n) O(nlog2n)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=2e5+50;
const int INF=0x3f3f3f3f;
const ll loo=1ll<<60;
double slope[MAXN];
struct enode{ll to,nxt,c; } e[MAXN<<1];
ll edgenum=0,smin,root,num,top;
ll st[MAXN],size[MAXN],vis[MAXN],dis[MAXN],f[MAXN],E[MAXN],head[MAXN],p[MAXN],q[MAXN],l[MAXN],fa[MAXN];
inline ll read()
{
    ll x=0,f=1; char c=getchar();
    while (c<'0'||c>'9') { if(c=='-') f=-1; c=getchar(); }
    while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+(c^48); c=getchar(); }
    return x*f;
}//快读模板
void add_edge(int u,int v,ll c) { e[++edgenum]=(enode){v,head[u],c}; head[u]=edgenum; }
void get_dis(int x) 
{ 
    for (int i=head[x];i!=-1;i=e[i].nxt) 
	    dis[e[i].to]=dis[x]+e[i].c,get_dis(e[i].to); 
}
void get_root(int x,int Size)
{
	size[x]=1; 
	ll mx=0;
	for (int i=head[x];i!=-1;i=e[i].nxt)
		if (!vis[e[i].to])
		{
			get_root(e[i].to,Size);
			size[x]+=size[e[i].to];
			mx=max(mx,size[e[i].to]);
		}
	mx=max(mx,Size-size[x]);
	if (smin>=mx) root=x,smin=mx;
}//获取根root
void build(int x)
{
	E[++num]=x;
	for (int i=head[x];i!=-1;i=e[i].nxt) 
	    if (!vis[e[i].to]) build(e[i].to);
}//建立树
double slp(int x,int y){ return (1.0*f[y]-f[x])/(1.0*dis[y]-dis[x]); }
int compareE(int x,int y){ return dis[x]-l[x]>dis[y]-l[y]; }
void insert(int x)
{
	while (top>=2&&slope[top-1]<=slp(st[top],x)) top--;
	st[++top]=x,slope[top]=-1e18,slope[top-1]=slp(st[top-1],st[top]);
}
ll query(double x)
{
	ll l=1,r=top,ans;
	while (l<=r)
	{
		ll mid=(l+r)>>1;
		if (slope[mid]<=x) ans=mid,r=mid-1;
		else l=mid+1;
	}
	return st[ans];
}
void solve(int x,int Size)
{
	if (Size==1) return;
	root=smin=INF;
	get_root(x,Size);
	ll rt=root,mx=smin;
	for (int i=head[rt];i!=-1;i=e[i].nxt) vis[e[i].to]=1,Size-=size[e[i].to];
	//cout<<Size<<" "<<rt<<" "<<mx<<endl;
	solve(x,Size);
	num=0;
	for (int i=head[rt];i!=-1;i=e[i].nxt) build(e[i].to);
	sort(E+1,E+num+1,compareE);
	
	top=0;
	for (int i=1,j=rt;i<=num;i++)
	{
		while (j!=fa[x]&&dis[j]>=dis[E[i]]-l[E[i]]) insert(j),j=fa[j];
		if (top)
		{
			int k=query(p[ E[i] ]);
			f[ E[i] ]=min(f[ E[i] ],f[k]+(dis[ E[i] ]-dis[k])*p[ E[i] ]+q[ E[i] ]);
		}
	}
	for (int i=head[rt];i!=-1;i=e[i].nxt) solve(e[i].to,size[e[i].to]);
}
int main()
{
	int n=read(),t=read();
	for (int i=1;i<=n;i++) head[i]=-1;
	for (int i=2;i<=n;i++) 
	{
		int u=read(),c=read(); p[i]=read(),q[i]=read(),l[i]=read(),f[i]=loo;
		add_edge(u,i,c); 
		fa[i]=u;
	} 
	get_dis(1);
	solve(1,n);
	for (int i=2;i<=n;i++) printf("%lld\n",f[i]);//printf节省时间
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值