题目:CF526B Om Nom and Dark Park
图论 - dfs - 树 - 贪心
题目大意
给出一个有
个节点的满二叉树,边带权
要求让一些边的边权增加,使得所有叶子节点到跟的路径上的边权之和相等。并让增加的权值最小
解题思路
这里讲一个非常贪心的做法
首先,我们找出离根节点最远的叶子节点,并算出他的距。在以后的计算中将不会在该路径上增加边权(不然会出现浪费)
接着,再使用 dfs 求出每个叶子节点到根的路径上,还需要加多少权值,我们用 表示
对于非叶子节点的 ,我们用来维护以该节点为根的子树中所有叶子节点的 最小值
void dfs1(int x,int cur)
{
tot=max(tot,cur); // tot 维护离根节点最远的点的距离
for(int i=0;i<e[x].size();++i)
{
int y=e[x][i].v;
dfs1(y,cur+e[x][i].len);
}
}
void dfs2(int x,int cur) // 求出所有叶子和非叶子节点的 dis[]
{
if(!e[x].size())
{
dis[x]=tot-cur; // 注意:叶子节点的 dis 储存的是还需要加多少边权
return;
}
dis[x]=inf;
for(int i=0;i<e[x].size();++i)
{
int y=e[x][i].v;
dfs2(y,cur+e[x][i].len);
dis[x]=min(dis[x],dis[y]);
}
}
然后,我们开始给边加权:
由于题目要求总共增加的权值最小,我们就要然加上的边权利用率最大
显然,边权加在离跟越近的地方,就可以让更多叶子节点收益,也就利用率越大。所以我们要尽可能地在离根近的地方给边加权
void dfs(int x,int cur) // cur 储存上方跟该子树有关的边已经加了多少权
{
for(int i=0;i<e[x].size();++i)
{
int y=e[x][i].v;
ans+=dis[y]-cur; // 上方已经加了 cur 的权,所以要减掉
dfs(y,dis[y]);
}
}
再给边加权是,要考虑到下方子树的所有叶子节点,不能加多(只能加上叶子节点中最小的 )。这时,非叶子节点的 就派上用场了
完整代码
#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
const int Maxn=2100,inf=0x3f3f3f3f;
struct edge{
int v,len;
edge(int x,int y)
{
v=x,len=y;
}
};
int dis[Maxn];
vector <edge> e[Maxn];
int n,m,tot,ans;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0' && ch<='9')s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*w;
}
void dfs1(int x,int cur)
{
tot=max(tot,cur);
for(int i=0;i<e[x].size();++i)
{
int y=e[x][i].v;
dfs1(y,cur+e[x][i].len);
}
}
void dfs2(int x,int cur)
{
if(!e[x].size())
{
dis[x]=tot-cur;
return;
}
dis[x]=inf;
for(int i=0;i<e[x].size();++i)
{
int y=e[x][i].v;
dfs2(y,cur+e[x][i].len);
dis[x]=min(dis[x],dis[y]);
}
}
void dfs(int x,int cur)
{
for(int i=0;i<e[x].size();++i)
{
int y=e[x][i].v;
ans+=dis[y]-cur;
dfs(y,dis[y]);
}
}
int main()
{
// freopen("in.txt","r",stdin);
m=read()+1,n=(1<<m)-1;
for(int i=2;i<=n;++i)
{
int x=(i>>1),y=i,c=read();
e[x].push_back(edge(y,c));
}
dfs1(1,0);
dfs2(1,0);
dfs(1,0);
printf("%d\n",ans);
return 0;
}