JZOJ6364. 【NOIP2019模拟2019.9.20】养马(horse)

49 篇文章 0 订阅
17 篇文章 0 订阅

Description

  • 给定一颗树,每个点可以收获体力值a[i]且最多收获一次。每条边需要消耗体力值w[i],且每次经过都会消耗。等待1个单位时间回复1点体力值。
  • 初始体力为0,求从1出发经过所有点并回到1,最小等待的时间。

n<=1e5

Solution

  • 很容易想到DP,设f[i]表示i的子树遍历完后需要等待的最小时间,那么剩余的体力res[i]=sum[i]+f[i]-cost[i],sum表示点权和,cost表示边权和*2
  • 那么影响到转移的实际上是儿子选取的顺序。
  • 跟顺序有关,并且转移又比较朴素,显然是可以贪心的。
  • 对于一个点x的所有儿子,如果以f[i]或res[i]排序显然是有问题的(我比赛的时候就打了这种显然错误的贪心。。。,后面的就没有想到了)。
  • 因为我们没有考虑到从x下去的这条边对答案的影响。
  • 所以我们改一下状态,将f[i]考虑上到i父亲那条边的边权的影响。
  • 现在我们再重新考虑这个问题的模型:由于如果要休息,在根节点休息是不劣的。那么如果要选择一个儿子y,那么首先至少要休息到f[y],并且体力会加上(res[y]-f[y])(设为g[y])
  • 我们现在要确定一个选择儿子的顺序使得刚开始休息的时间最少。
  • 对于g[y]>=0的,说明原来是f[y],进去之后就变多了,显然我们可以按照f从小到大选取保证每一次的要补充的体力最少。
  • 对于g[y]<0的,我们倒着选,假定最后剩下了s,那么反过来,s+|g[y]|>=f[y]才可以选择y。
  • 即s>=f[y]-|g[y]|=f[y]+g[y],就可以选择y,并且s可以加|g[y]|。这与g[y]>=0的一模一样!
  • 所以对于f[y]+g[y]从大到小选择就是原本的顺序啦!
  • 思路巧妙(其实也可说很水)
  • 贪心太难啦~
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define ll long long 
#define maxn 100005
using namespace std;

int n,i,j,k,x,y,z;
int em,e[maxn*2],nx[maxn*2],ls[maxn];
ll a[maxn],ec[maxn*2],f[maxn],res[maxn],ecf[maxn];
int tot0,d0[maxn],tot1,d1[maxn];
int cmp1(int x,int y){return f[x]<f[y];}
int cmp2(int x,int y){return f[x]+res[x]>f[y]+res[y];}

void insert(int x,int y,int z){
	em++; e[em]=y; nx[em]=ls[x]; ls[x]=em; ec[em]=z;
	em++; e[em]=x; nx[em]=ls[y]; ls[y]=em; ec[em]=z;
}

void DFS(int x,int p){
	for(int i=ls[x];i;i=nx[i]) if (e[i]!=p)
		ecf[e[i]]=ec[i],DFS(e[i],x);
		
	f[x]=ecf[x];
	tot0=tot1=0;
	for(int i=ls[x];i;i=nx[i]) if (e[i]!=p){
		if (res[e[i]]>=0) d0[++tot0]=e[i];
		else d1[++tot1]=e[i];
	}
	sort(d0+1,d0+1+tot0,cmp1);
	sort(d1+1,d1+1+tot1,cmp2);
	ll s=a[x];
	for(int i=1;i<=tot0;i++) {
		if (s>=f[d0[i]]) s+=res[d0[i]];
		else f[x]+=f[d0[i]]-s,s=f[d0[i]]+res[d0[i]];
	}
	for(int i=1;i<=tot1;i++) {
		if (s>=f[d1[i]]) s+=res[d1[i]];
		else f[x]+=f[d1[i]]-s,s=f[d1[i]]+res[d1[i]];
	}
	if (s>=ecf[x]) s-=ecf[x]; else f[x]+=ecf[x]-s,s=0;
	res[x]=s-f[x];
}

int main(){
	freopen("horse.in","r",stdin);
	freopen("horse.out","w",stdout);
	scanf("%d",&n);
	for(i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(i=1;i<n;i++){
		scanf("%d%d%d",&x,&y,&z);
		insert(x,y,z);
	}
	DFS(1,0);
	printf("%lld\n",f[1]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值