Acwing 287. 积蓄程度

有一个树形的水系,由 N−1 条河道和 N 个交叉点组成。

我们可以把交叉点看作树中的节点,编号为 1∼N,河道则看作树中的无向边。

每条河道都有一个容量,连接 xx 与 yy 的河道的容量记为 c(x,y)。

河道中单位时间流过的水量不能超过河道的容量。

有一个节点是整个水系的发源地,可以源源不断地流出水,我们称之为源点。

除了源点之外,树中所有度数为 1 的节点都是入海口,可以吸收无限多的水,我们称之为汇点。

也就是说,水系中的水从源点出发,沿着每条河道,最终流向各个汇点。

在整个水系稳定时,每条河道中的水都以单位时间固定的水量流向固定的方向。

除源点和汇点之外,其余各点不贮存水,也就是流入该点的河道水量之和等于从该点流出的河道水量之和。

整个水系的流量就定义为源点单位时间发出的水量。

在流量不超过河道容量的前提下,求哪个点作为源点时,整个水系的流量最大,输出这个最大值。

输入格式

输入第一行包含整数 T,表示共有 T 组测试数据。

每组测试数据,第一行包含整数 N。

接下来 N−1 行,每行包含三个整数 x,y,z,表示 x,y之间存在河道,且河道容量为 z。

节点编号从 1 开始。

输出格式

每组数据输出一个结果,每个结果占一行。

数据保证结果不超过 2^31−1。

数据范围

N≤2×10^5

输入样例:

1
5
1 2 11
1 4 13
3 4 5
4 5 10

输出样例:

26

题目大意:以树的形式给出一个水系,由N个结点和N-1条边组成,边表示河道,每条河道有一个单位时间最大水流量,点表示河道的交叉点,交叉点中有两类比较特殊的点一类叫做源点,从该点可以留出无穷多的水,一类叫做汇点,可以有无穷多的水流入该点,让我们选择一个点作为源点,得到单位时间内的最大水流量。

下面是我一开始写的一个暴力做法,最终会T掉。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N=2E5+10;
int h[N],e[N<<1],w[N<<1],ne[N<<1],idx,f[N];
int n,ans;
void add(int u,int v,int val)
{
    e[idx]=v;
    ne[idx]=h[u];
    w[idx]=val;
    h[u]=idx++;
}
void dfs(int u,int fa)
{
    int t;
    int flag=0;
    if(fa==-1)//如果说当前结点是源点 
    t=0x3f3f3f3f;//t记录的是从u的父节点到u的河道所能承载的最大水流量 
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==fa)
        {
            t=w[i];  
            continue;
        }
        flag=1;
        dfs(j,u);
        f[u]+=f[j];//f[u]暂时表示u的所有子节点所能通过的最大水流量之和 
    }
    if(flag==0)//如果u是叶子结点那么其叶子结点所能通过的最大水流量是无穷大 
    f[u]=0x3f3f3f3f;
    f[u]=min(f[u],t);//更新f[u],得到能通过u点的最大水流量 
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n;
        memset(h,-1,sizeof h);
        idx=0;
        ans=0;
        for(int i=1;i<n;i++)
        {
            int x,y,z;
            cin>>x>>y>>z;
            add(x,y,z);
            add(y,x,z);
        }
        for(int i=1;i<=n;i++)//枚举每个点作为源点,所有情况中取一个最大值 
        {
            memset(f,0,sizeof f);
            dfs(i,-1);
            ans=max(ans,f[i]);
        }
        cout<<ans<<endl;
    }
    return 0;
}

现在我们的任务就是如何对其进行优化,这段暴力代码中,时间复杂度最高的是枚举每一个点作为源点进行搜索,所以我们要在这个地方进行优化,优化方法y总讲的是用换根法。换根法的一般思路如下:(1)、首先从底向上递推,将对问题求解有帮助的信息先计算出来,并进行存储。(2)、从上往下递推,这一步就是要得到我们最终的解,这一步会用的信息已经在上一步中求出并记录,这里只需要通过一定的计算公式即可得到答案。

对于这个题来说,第一步中我们需要记录 一个d[u],表示不考虑u和父节点之间河道的承载量,能经过u点流向u点孩子结点的最大流量,d[u]的值等于所有min(d[son],c(u,son))之和,c(u,son)表示u和son之间河道的最大承载量,如果当前结点是汇点,那么能够通过该节点的最大流量记为无穷大。第二步要求出f[u],表示以u为源点所能通过的最大流量,在第一步中我们首先要选择一个点作为根节点开始扩展求解,记该点为root,所以在第二步中f[root]=d[root],然后从root点开始向下扩展,扩展的时候分两种情况,一种情况是如果新扩展出来的结点j是叶子结点,那么f[j]=min(f[u]-w[i],w[i]),如果j不是叶子结点,那么f[j]=d[j]+min(f[u]-min(d[j],w[i],w[i]),至于为什么要分开求解因为如果j为叶子结点,那么d[j]就为正无穷,从不是叶节点的递推公式来看,d[j]显然是不能再加了,然后min(d[j],w[i])=w[i],所以才有了第一个递推公式。

最后再来说一个比较坑的点,在第一步中我们首先要选择一个根节点,这个根节点的选择其实是有一定讲究的,就是这个根节点不能选择度为1的点作为根节点,否则就会被下面两组数据给卡掉。

Input:

1
3
1 2 1
2 3 1
Ans

2(如果随便选择根节点,答案就是无穷大,下面的样例结果同样如此)

Input:

1
2
1 2 1
Ans

1

如果最终我们发现所有节点的度都是1,说明这棵树一共就两个结点,一条边,直接输出w[0]即可。

上正确代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N =2e5+10;
int h[N],e[N<<1],ne[N<<1],w[N<<1],idx,f[N],n,d[N];
int deg[N];//记录每个点的度
void add(int u,int v,int val)
{
    e[idx]=v;
    ne[idx]=h[u];
    w[idx]=val;
    h[u]=idx++;
}
int dfs_d(int u,int fa)
{
    if(deg[u]==1)//u为叶子结点
    {
        d[u]=0x3f3f3f3f;
        return d[u];
    }
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==fa)
        continue;
        d[u]+=min(w[i],dfs_d(j,u));
    }
    return d[u];
}
void dfs_f(int u,int fa)
{
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(j==fa)
        continue;
        if(deg[j]==1)
        f[j]=min(f[u]-w[i],w[i]);
        else
        {
            f[j]=d[j]+min(f[u]-min(d[j],w[i]),w[i]);
            dfs_f(j,u);
        }
    }
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>n;
        memset(h,-1,sizeof h);
        memset(deg,0,sizeof deg);
        memset(d,0,sizeof d);
        memset(f,0,sizeof f);
        idx=0;
        for(int i=1;i<n;i++)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,c);
            add(b,a,c);
            deg[a]++;
            deg[b]++;
        }
        int root=1;//找到度不为1的点作为叶子结点
        while(root<=n&&deg[root]==1)
        root++;
        if(root>n)
        {
            cout<<w[0]<<endl;
            continue;
        }
        dfs_d(root,-1);
        f[root]=d[root];
        dfs_f(root,-1);
        int res=0;
        for(int i=1;i<=n;i++)
        res=max(res,f[i]);
        cout<<res<<endl;
    }
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值