●BZOJ 3672 [Noi2014]购票

 

题链:

http://www.lydsy.com/JudgeOnline/problem.php?id=3672

题解:

斜率优化DP,点分治(树上CDQ分治...)

这里有一个没有距离限制的简单版:BZOJ 1767 [Ceoi2009]harbingers

定义$DP[i]$为从i出发到1号点的最小花费,$dis_i$为i到1号点的距离:

转移:

$DP[i]=min(DP[j]+(dis_i-dis_j)P_j)+Q_i$

$\quad=min(DP[j]-dis_jP_i)+dis_iP_i+Q_i$

显然这个$O(N^2)$的转移会超时。


考虑优化:

假设对于当前计算的DP[i],有两个转移来源点:k,j,同时dis[k]<dis[j],设j点比k点优。

那么有:$DP[j]-dis_j*P_i-(DP[k]-dis_k*P_i)<0$

则: $\frac{DP[j]-DP[k]}{dis[j]-dis[k]}<P[i]$

如果令 Slope(j,k)=$\frac{DP[j]-DP[k]}{dis[j]-dis[k]}$

那么得到结论,如果 $dis_k<dis_j$,且Slope(j,k)<P[i]的话,则j点优于k点。

同时如果存在三个转移来源点:k,j,i,满足$dis_k<dis_j<dis_i$,

同时Slope(i,j)<Slope(j,k),则j点无效。

所以对于每个来源点二元组(dis[j],DP[j]),只需要在平面上维护一个下凸壳即可。

 


1).如果问题不在一颗树上,而是在一个序列上,这个应该比较容易吧:一个裸的CDQ就可以解决了。

2).而如果问题没有limit这个距离限制,即使在树上,也比较容易了,因为随着DFS遍历树时,dis是单增的,所以可以直接维护单调栈(详细见BZOJ 1767 [Ceoi2009]harbingers

 

但是现在既在树上又有距离限制怎么办呢?这里采用点分治来实现CDQ分治的功能,(可以叫做树上CDQ分治么)

对于当前的子树,我们令根为u,(这个根是子树内的点在前往1号点时都要经过的地方)。

并找到子树内的重心cg(the center of gravity),

然后先递归处理cg为根时含有u的那颗子树,

不难发现,cg的其它子树的点(令这些点的集合为R)在前往1号点时,都会进过cg~u这一条链,

即这条链上的点可能会成为R里的点的转移点。

同时为了满足距离这一限制,即每个点i最多只能向上到达 $dis_i-limit_i$这个位置

所以把R里的点按 $dis_i-limit_i$排序后,从大到小枚举R里的i点,并把cg~u这条链上dis小于$dis_i-limit_i$的点用单调栈维护一个下凸壳。

接着在凸壳里二分最优的转移来源点即可。

整个过程是$O(Nlog_2^2N)$的。


(伤不起,读入居然要long long!)

代码:PoPoQQQ的代码写得很棒呀!

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 200050
#define ll long long
using namespace std;
ll DP[MAXN],P[MAXN],Q[MAXN],G[MAXN],dis[MAXN];
int fa[MAXN],siz[MAXN];
int N,t;
struct Edge{
	ll val[MAXN];
	int to[MAXN],nxt[MAXN],head[MAXN],ban[MAXN],ent;
	Edge(){ent=2;}
	void Adde(int u,int v,ll w){
		to[ent]=v; val[ent]=w; nxt[ent]=head[u]; head[u]=ent++;
	}
}E;
struct Mostk{
	int s[MAXN],top;
	#define Slope(i,j) (1.0*(DP[i]-DP[j])/(dis[i]-dis[j]))
	void Reset(){top=0;}
	void Push(int i){
		if(top&&dis[s[top]]==dis[i])
			{if(DP[i]<DP[s[top]]) top--; else return;}
		while(top>1&&Slope(s[top-1],s[top])<Slope(s[top],i)) top--;
		s[++top]=i;
	}
	int Query(int i){//二分单调栈
		static int l,r,mid,ret;
		if(!top) return 0;
		l=2; r=top; ret=1;
		while(l<=r){
			mid=(l+r)>>1;
			if(Slope(s[mid-1],s[mid])>=P[i]) ret=mid,l=mid+1;
			else r=mid-1;
		}
		return s[ret];
	}
}S;
void dfs(int u){
	for(int i=E.head[u];i;i=E.nxt[i]){
		int v=E.to[i];
		dis[v]=dis[u]+E.val[i];
		dfs(v);
	}
}
void getcg(int u,int &cg,int &num,int sum){
	int maxnum=0; siz[u]=1;
	for(int i=E.head[u];i;i=E.nxt[i]){
		int v=E.to[i]; if(E.ban[i]) continue;
		getcg(v,cg,num,sum);
		maxnum=max(maxnum,siz[v]);
		siz[u]+=siz[v];
	}
	maxnum=max(maxnum,sum-siz[u]);
	if(maxnum<=num) num=maxnum,cg=u;
}
void trave(int u,int &cr,int *R){
	R[++cr]=u;
	for(int i=E.head[u];i;i=E.nxt[i]){
		if(E.ban[i]) continue;
		trave(E.to[i],cr,R);
	}
}
bool cmp(int i,int j){
	return dis[i]-G[i]>dis[j]-G[j];
}
void solve(int u,int num){
	static int R[MAXN],cr,maxnum;
	if(num==1) return; 
	int cg=u; maxnum=num;
	getcg(u,cg,maxnum,num);
	//-----------------------------准备递归u区域
	for(int i=E.head[cg];i;i=E.nxt[i]) E.ban[i]=1;
	solve(u,num-siz[cg]+1);
	//-----------------------------开始解决当前层的贡献
	cr=0; S.Reset();
	for(int i=E.head[cg];i;i=E.nxt[i]) trave(E.to[i],cr,R);
	sort(R+1,R+cr+1,cmp);
	for(int i=1,j=cg,k;i<=cr;i++){
		while(j!=fa[u]&&dis[j]>=dis[R[i]]-G[R[i]]) S.Push(j),j=fa[j];
		k=S.Query(R[i]);
		if(k) DP[R[i]]=min(DP[R[i]],DP[k]+(dis[R[i]]-dis[k])*P[R[i]]+Q[R[i]]);
	}
	//-----------------------------递归解决剩下子树区域
	for(int i=E.head[cg];i;i=E.nxt[i])
		solve(E.to[i],siz[E.to[i]]);
}
void read(ll &x){
	static int sn; static char ch;
	x=0; sn=1; ch=getchar();
	while(ch<'0'||'9'<ch){if(ch=='-')sn=-1;ch=getchar();}
	while('0'<=ch&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	x=x*sn;
}
int main(){
	scanf("%d%d",&N,&t);
	memset(DP,0x3f,sizeof(DP));
	DP[1]=0; ll f,s,p,q,g;
	for(int i=2;i<=N;i++){
		read(f); read(s); read(p); read(q); read(g);
		fa[i]=f; P[i]=p; Q[i]=q; G[i]=g;
		E.Adde(f,i,s);
	}
	dfs(1);
	solve(1,N);
	for(int i=2;i<=N;i++) printf("%lld\n",DP[i]);
	return 0;
}

  

 

转载于:https://www.cnblogs.com/zj75211/p/8168827.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值