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


    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
    ",f[1]);
    }
    
  • 相关阅读:
    C#中静态变量 静态类 静态成员
    C#类型转换
    ASP.NET页面跳转及传值方式
    ADO.NET
    C#方法的参数类型
    Oracle %TYPE 和 %ROWTYPE
    静态页面与动态页面
    相对路径和绝对路径
    45 个非常有用的 Oracle 查询语句(转)
    ASP.NET MVC 之表格分页
  • 原文地址:https://www.cnblogs.com/DeepThinking/p/13090959.html
Copyright © 2020-2023  润新知