NKOJ 4974 「NOIP模拟」乌鸦坐飞机【倍增优化DP】

####题目描述

q q q只乌鸦要坐机到同一个地方看阿福揍扁成龙!坐飞机就要到飞机场,一共 n n n个飞机场构成了一棵树,编号为 1 1 1 n n n,其中 1 1 1号点是所有乌鸦的目的地,所以可以把飞机场的结构视为一棵以 1 1 1号点为根的有根树。坐飞机就要买飞机票,每个机场都只售 1 1 1种飞机票,我们用 v , d v,d v,d两个参数描述一张飞机票,表示这张飞机票售价为 v v v,可以让乌鸦飞到从当前机场到 1 1 1号机场的路径上距离不超过 d d d的任意 1 1 1个机场(两个机场的距离定义为两点之间简单路径上的边数)。
由于一些规定,一只乌鸦同时只能持有至多一张飞机票,每张飞机票只能使用一次。每只乌鸦都很理性,不在乎转多少趟飞机,只要总费用最少就行。请你计算每只乌鸦的最小总费用。


f [ x ] f[x] f[x]表示从 x x x号点出发到达根节点的最小总费用,那么转移如下( y y y x x x的祖先):
f [ x ] = m i n { f [ y ] } + v [ x ] f[x]=min\{f[y]\}+v[x] f[x]=min{f[y]}+v[x]

而如果我们暴力找出最小的 f [ y ] f[y] f[y]的话,时间复杂度在 O ( l o g 2 2 n ) − > O ( n 2 ) O(log_2^2n)->O(n^2) O(log22n)>O(n2)级别,超级不稳定,所以我们考虑优化求最小值的过程。

注意到这是在树上进行 D P DP DP,于是我们可以用倍增来维护这个最小值(作为一个连 S t St St表都用线段树代替的蒟蒻确实是第一次除了倍增 L C A LCA LCA以外写倍增了)。

代码实现其实就与倍增求 L C A LCA LCA十分类似了:

#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
#define LL long long
#define For(I,X,Y) for(LL I=(X);I<=(Y);I++)
#define ForDown(I,X,Y) for(LL I=(X);I>=(Y);I--)
using namespace std;

inline LL Read(){
	LL X=0;char CH=getchar();bool F=0;
	while(CH>'9'||CH<'0'){if(CH=='-')F=1;CH=getchar();}
	while(CH>='0'&&CH<='9'){X=(X<<1)+(X<<3)+CH-'0';CH=getchar();}
	return F?-X:X;
}

const LL Max=3e5+5;
const LL Inf=1e18;
LL DP[Max];
LL N,M,V[Max],D[Max];
LL Crow,Fa[Max][20],Dis[Max][20];
LL Cnt,To[Max<<1],Next[Max<<1],Head[Max];

#define Update(X,Y) ((X)=(X)>(Y)?(Y):(X))
#define ReNew(X,Y,Z) ((X)=(Z)>(Y)?(Y):(Z))
#define Search(X,I) for(LL I=Head[X];I;I=Next[I])

void Insert(LL X,LL Y){Cnt++;To[Cnt]=Y;Next[Cnt]=Head[X];Head[X]=Cnt;}

void DFS(LL X){
	For(I,1,19) Fa[X][I]=Fa[Fa[X][I-1]][I-1],ReNew(Dis[X][I],Dis[X][I-1],Dis[Fa[X][I-1]][I-1]);
	
	if(X!=1){
		LL Loc=X,Delta=D[X];
		DP[X]=Inf;
		For(I,0,19){
			if(Delta&(1<<I)){
				Update(DP[X],Dis[Loc][I]);Loc=Fa[Loc][I];
			}
		}
		DP[X]+=V[X];
	}
	
	Search(X,I){
		LL Y=To[I];
		if(Y!=Fa[X][0]){
			Fa[Y][0]=X;Dis[Y][0]=DP[X];DFS(Y);
		}
	}
}

int main(){
	N=Read();
	
	For(I,1,N-1){
		LL X=Read(),Y=Read();
		Insert(X,Y);Insert(Y,X);
	}
	
	For(I,1,N) V[I]=Read(),D[I]=Read();
	
	DFS(1);
	
	M=Read();
	
	For(I,1,M) printf("%lld\n",DP[Read()]);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值