换根dp入门

模板题

题目大意

本题是要你任选一个点为根使得树上的所有点深度之和最小。

题目思路

本题是要你任选一个点为根使得树上的所有点深度之和最小。

首先考虑如果是指定根节点你会不会做:已知根节点的话,我们只需要一遍dfs(或者bfs)就可以求出每个点的深度,然后求和就可以了。

然后我们考虑如果我们知道当前节点x为根的结果能否快速求出以它某儿子y为根的结果——当y从根的儿子变成根,它和它的子树都向上提了一层,深度-1,而x往下了一层,它除了y以外的其他子树也一起往下了一层,深度加以。所以从以x为根变为以y为根,深度之和减少了y的子树的大小,增加了其他点数的个数。
在这里插入图片描述
如果我们用f[i]表示以i为根的时候每个点深度的和,我们可以用一遍dfs把f[1]求出来——以1为根dfs,给每个点维护一个dep[i],显然dep[y] = dep[x]+1(y是x的儿子),dfs结束之后求和就好。根据之前的分析在这次dfs的时候我们还需要同时维护一个数组cnt[i]表示i的子树大小(包括它自己),cnt[x]=∑cnt[y](y是x的儿子)。然后再来一次dfs,遍历到x点的时候,去计算x的每个儿子y的f值,f[y]= f[x] -cnt[y] + (n- cnt[y])

最后只需要取f[i]的最小值就是答案。

这种解法实际上是利用了换根的思路,即先算出固定某一点为根的答案然后考虑把它的儿子换成根会发生什么样的变化,如果这个变化是比较好算的,那么我们就可考虑每个点x为根的答案都根据以它父亲为根的结果去推。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define debug printf("I am here\n");
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
const ll INF=0x3f3f3f3f3f3f3f3f;
int n,cnt,head[maxn],dep[maxn],sz[maxn];
ll ans[maxn],mindep=INF;//ans[i],以i为根的ans
struct node{
    int to,next;
}e[maxn<<1];
void add(int u,int v){
    e[++cnt].to=v;
    e[cnt].next=head[u];
    head[u]=cnt;
}
void dfs1(int son,int fa){
    sz[son]=1;
    for(int i=head[son];i;i=e[i].next){
        if(e[i].to==fa) continue;
        dep[e[i].to]=dep[son]+1;
        dfs1(e[i].to,son);
        sz[son]+=sz[e[i].to];
    }
    ans[1]+=dep[son];
}
void dfs2(int son,int fa){
    for(int i=head[son];i;i=e[i].next){
        if(e[i].to==fa) continue;
        ans[e[i].to]=ans[son]-sz[e[i].to]+(n-sz[e[i].to]);
        dfs2(e[i].to,son);
    }
    mindep=min(mindep,ans[son]);
}
int main(){
    scanf("%d",&n);
    for(int i=1,u,v;i<=n-1;i++){
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    dfs1(1,1);
    dfs2(1,1);
    printf("%lld\n",mindep);
    return 0;
}

题目链接

题目思路

首先,我们来看是不是能求出以某固定点(以1为例)的流量:

我们用flow[i]表示i点的流量:

flow[i]=∑min(flow[j],edge[i][j])(j是i的儿子)

这里特别要注意i点是叶子节点的情况,我们可以认为它的flow就是连向它的边流量就可以了。

接着,我们来考虑换根,flow[i]表示以i为根的流量——考虑当根从x变成x的儿子y的时候会发生什么?哪些量边哪些量不变? (想不清楚就画图,做算法题“草稿纸”还是很重要的。)
在这里插入图片描述

x的流量中没有从y过来的部分了 ,即此时x的流量其实是flow[x] - min(flow[y], edge[x] [y])但是此时的flow我们用它来表示以i为根的流量了(其实你也可以用另外一个数组存,但是并没有必要),所以并不更新flow[x]。

而x的流量会流向y,注意这里是x的新流量,而不是flow[x]。flow[y] = flow[y] + min(flow[x] - min(flow[y], edge[x] [y]), edge[x] [y])

特别注意叶子结点,当y是叶子节点的时候没关系,但是当x从根变为了叶子节点(说明x除了y这个儿子没有别的儿子了),它的流量并不是变成的上面的式子,而是直接变成edge[x] [y]。

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define debug printf("I am here\n");
using namespace std;
typedef long long ll;
const int maxn=2e5+5,inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
int t,n,cnt,head[maxn],flow[maxn],deg[maxn],ma;
struct node{
    int to,next,w;
}e[maxn<<1];
void add(int u,int v,int w){
    e[++cnt].to=v;
    e[cnt].next=head[u];
    e[cnt].w=w;
    head[u]=cnt;
}
void dfs1(int son,int fa){
    for(int i=head[son];i;i=e[i].next){
        if(e[i].to==fa) continue;
        if(deg[e[i].to]==1){
            flow[e[i].to]=e[i].w;
        }
        dfs1(e[i].to,son);
        flow[son]+=min(flow[e[i].to],e[i].w);
    }
}
void dfs2(int son,int fa){
    for(int i=head[son];i;i=e[i].next){
        if(e[i].to==fa) continue;
        flow[e[i].to]+=min(e[i].w,flow[son]-min(flow[e[i].to],e[i].w));
        dfs2(e[i].to,son);
    }
}
void init(){
    ma=cnt=0;
    for(int i=1;i<=n;i++){
        flow[i]=head[i]=deg[i]=0;
    }
}
int main(){
    scanf("%d",&t);
    while(t--){
         scanf("%d",&n);
         init();
        for(int i=1,u,v,w;i<=n-1;i++){
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w),add(v,u,w);
            deg[u]++,deg[v]++;
        }
        dfs1(1,1);
        dfs2(1,1);
        for(int i=1;i<=n;i++){
            ma=max(ma,flow[i]);
        }
        printf("%d\n",ma);
    }
    return 0;
}

参考链接:https://ac.nowcoder.com/discuss/406025?type=101&order=0&pos=5&page=1

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值