zoukankan      html  css  js  c++  java
  • CF77C Beavermuncher-0xFF

    题意简述

    题目链接

      给定一棵树,每个节点有一个权值k,表示该节点有多少个海狸,从根节点出发,每吃一个海狸便能够且必须跳到与当前节点有直接边相连的节点上,要求最终跳回根节点,求最多能吃多少个海狸。

    算法概述

      考虑每个节点产生的贡献。

      首先明确一点:每个节点产生的贡献与且只与其儿子节点有关。

      先dfs递归计算出每个儿子的贡献。然后考虑当前节点,若当前节点u不是树根,则先将k[u]减去1(因为还要跳回父亲,需要吃掉1个海狸)。

      设sons[u]为节点u的儿子数,计算当前节点的贡献:

      (i) 若k[u]<sons[u],即无法吃遍所有儿子,则将儿子的贡献值排序,从大到小吃,每吃一个儿子将k[u]减1,直到k[u]减为0结束。

      (ii) 若k[u]>=sons[u],说明可以吃遍所有儿子,那么进行完(i)中的操作(即吃遍所有儿子)之后k[u]还有剩余,记为last,则考虑在u与其儿子节点之间来回跳跃,sum统计所有儿子的剩余权值之和,那么还可产生的贡献即为2*min(last,sum)。

      时间复杂度分析:

      不难发现,时间复杂度的瓶颈主要在对所有儿子的贡献值进行排序的操作上。

      设每个节点的儿子数量为s,则总时间=s1logs1+s2logs2+……+snlogsn<=s1logn+s2logn+……+snlogn=(s1+s2+……+sn)logn=(n-1)logn。

      故时间复杂度为O(nlogn)

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    
    #define x first
    #define y second
    
    using namespace std;
    
    typedef long long ll;
    typedef pair<ll,ll> pll; //从该点出发往下跳的贡献值,剩余权值 
    const int N=1e5+10;
    
    struct Edge{
    	int to,next;
    }edge[N<<1];int idx;
    int h[N];
    
    int k[N];
    int n,root;
    
    void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}
    
    pll dfs(int p,int fa)
    {
    	vector<ll> v; //所有儿子的贡献放入vector中 
    	ll sum=0;
    	int flag=1; //是否为叶子节点 
    	for(int i=h[p];~i;i=edge[i].next)
    	{
    		int to=edge[i].to;
    		if(to==fa)continue;
    		flag=0;
    		pll s=dfs(to,p);
    		sum+=s.y;
    		v.push_back(s.x);
    	}
    	if(flag)return make_pair(0,k[p]-1); 
    	/*
    	由于第一维是统计从该点出发往下跳,最后跳回来的总贡献值,
    	而该点为叶子节点,故为0。
    	第二维即从该点跳回父亲之后,剩余的权值,
    	由于跳回父亲需要吃掉一个海狸
    	故为k[p]-1。
    	而其跳回父亲这一步的贡献会在其父亲节点处计算。 
    	*/ 
    	
    	sort(v.begin(),v.end());
    	
    	ll last=k[p]-(p==root?0:1),eat=0; //若为根,则不必跳回父亲,否则需要跳回父亲,故减1。 
    	for(int i=v.size()-1;i>=0&&last;i--,last--)eat+=v[i]+2; 
    	//v[i]为以该儿子为根的子树的总贡献,而后需要加上从该点跳到该儿子的贡献1以及从该儿子跳回该点的贡献1,故加2。 
    	eat+=2*min(last,sum); //来回跳跃,一来一回即产生了2的贡献,故而是两倍的跳跃步数。 
    	last-=min(last,sum); //维护剩余权值,来回跳跃之后需要减去跳跃步数。 
    	//此处上面两行不必考虑last是否已经减为0,因为若last为0的话,则即使执行上面两行,也不会对答案产生影响。 
    	return make_pair(eat,last);
    }
    
    int main()
    {
    	memset(h,-1,sizeof h);
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)scanf("%d",&k[i]);
    	
    	for(int i=1;i<=n-1;i++)
    	{
    		int u,v;scanf("%d%d",&u,&v);
    		add_edge(u,v),add_edge(v,u); 
    	}
    	
    	scanf("%d",&root);
    	
    	printf("%lld
    ",dfs(root,0).x);
    	
    	return 0;
    }
    

      

  • 相关阅读:
    redis
    sqlalchemy ORM
    元类的理解
    python连接mysql
    ffmpeg去水印
    ffmpeg给视频加文字水印
    yt-seo-checklist
    ffmpeg下载直播流
    ffmpeg拼接mp4视频
    ffmpeg截取视频
  • 原文地址:https://www.cnblogs.com/ninedream/p/13427380.html
Copyright © 2011-2022 走看看