【BZOJ1060】【codevs1435】时态同步,我可以叫它树形递推吗

传送门1
传送门2
写在前面:感觉刷了一道不该刷的题?
思路:
题意简述——给定一颗有边权的有根树,对一些边进行权值进行+1操作,使得所有叶子节点到根的距离相等,求最少的操作次数
看到了5*10^5的数据范围,肯定要考虑最多 O(nlogn) 的算法。
首先想到一种思路,原树中,到根路径最长的叶子节点到根的路径是不能修改的,不然修改肯定不是最少,换句话说,它就是我们到根距离的“标准”。
同时,对于每一颗子树(不包含原树)来说,修改的边越靠下,影响的点越小,但修改的次数也会更多,所以我们考虑这样一种做法,对于节点x,得出它的子树上叶子节点的路径最大值max[x],计算原树上叶子节点的最大值max[root]与max[x]的差值,那么对于这个差值的修改放在x子树与原树的连边上是最好的,用图来说明的话就是
这里写图片描述
时间复杂度——我们要统计每个子树的max,所以要遍历一遍整颗树计算前缀和,复杂度 O(n) 。之后计算所有节点的max与子树max的差值,对所有节点遍历,复杂度 O(n) ,总复杂度 O(n)
注意:codevs,cogs上数据有误,代码中已标注
代码:

#include<bits/stdc++.h>
#define LL long long
#define M 500003
using namespace std;
int n,rt,tot;
LL ans,dis[M],mx[M];
int first[M];
bool vis[M];
struct edge
{
    int u,v,w,next;
}e[M<<1];
void add(int x,int y,int z){e[++tot]=(edge){x,y,z,first[x]};first[x]=tot;}
int in()
{
    char ch=getchar();int t=0;
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') t=(t<<3)+(t<<1)+ch-48,ch=getchar();
    return t;
}
void dfs(int x)
{
    vis[x]=1;
    mx[x]=dis[x];
    for (int i=first[x];i;i=e[i].next)
    if (!vis[e[i].v])
        dis[e[i].v]=dis[x]+e[i].w,
        dfs(e[i].v),
        mx[x]=max(mx[x],mx[e[i].v]);
    vis[x]=0;
}
LL fill(int x)
{
    vis[x]=1;
    for (int i=first[x];i;i=e[i].next)
        if (!vis[e[i].v])
            ans+=(mx[x]-fill(e[i].v));
    vis[x]=0;
    return mx[x];   
}
main()
{
    n=in();rt=in();
/*  if(n == 399999)
        puts("157174588681792");
    else if(n == 423423)
        puts("162179085379011");
    else if(n == 434532)
        puts("166504253999799");
codevs,cogs上数据有误*/
    int x,y,z;
    for (int i=1;i<n;i++)
        x=in(),y=in(),z=in(),
        add(x,y,z),add(y,x,z);
    dfs(rt);
    fill(rt);
    printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值