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\n",dfs(root,0).x);
return 0;
}

浙公网安备 33010602011771号