Acwing 287. 积蓄程度

本文探讨了一种树形结构的水系模型,通过优化算法确定最佳水源点以实现最大流量。介绍了暴力解法及其时间复杂度过高的问题,并提出采用换根法优化,通过两阶段递归计算最大可能流量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有一个树形的水系,由 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;
}

### 解决Java中出现的`UnsatisfiedLinkError`问题 #### 一、原因分析 `java.lang.UnsatisfiedLinkError` 是一种运行时异常,表示 Java 虚拟机(JVM)在尝试调用本地方法时失败。以下是可能导致该错误的主要原因: 1. **本地库文件缺失** 如果目标本地库文件(如 `.dll`, `.so`, 或 `.dylib` 文件)不存在于指定路径,则 JVM 将无法加载所需的函数实现[^1]。 2. **路径配置不当** 即使本地库文件存在于系统中,但如果 `java.library.path` 环境变量未正确设置,JVM 仍然无法定位到这些库文件[^3]。 3. **依赖项不足** 某些本地库可能依赖其他动态链接库或共享对象文件。如果这些依赖项未能成功加载,也会引发此错误[^4]。 4. **架构不匹配** 当使用的 JDK 本地库之间的位数不同步(例如,在 64 位操作系统上使用了 32 位的本地库),则会出现兼容性问题[^5]。 5. **JNI 接口定义冲突** 若 Java 中声明的 native 方法与 C/C++ 实现部分的方法签名不符,同样会导致此类错误发生。 --- #### 二、解决方案 针对上述每种情况,提供相应的解决策略如下: 1. **验证本地库是否存在** - 首先确认所需本地库确实已经放置在项目目录或其他可访问的位置。 - 对于 OpenCV 的例子来说,需确保 opencv 库对应的 dll 文件已被复制至系统的特定路径,比如 Windows 下的 `C:\Windows\System32`[^2]。 2. **调整 java.library.path 参数** - 在启动应用前通过命令行参数 `-Djava.library.path=<path_to_native_libraries>` 来显式指明本地库所在位置。 示例代码: ```bash java -Djava.library.path=/usr/local/lib -jar your-application.jar ``` 3. **检查依赖链完整性** - 使用工具如 `ldd` (Linux 平台)、Dependency Walker(Windows 平台)检测是否有任何间接依赖丢失的情况,并补充安装相应组件。 4. **统一平台架构一致性** - 根据当前计算机体系结构选择相适应版本的 JDK/JRE 及其配套资源包;对于多核处理器建议优先选用支持更广泛指令集优化后的 64-bit 版本软件产品。 5. **校验 JNI 函数映射准确性** - 开发者应仔细对比双方源码文档说明材料,保证两者之间保持高度一致性的接口设计风格。 --- #### 三、注意事项 除了以上提到的内容外还需要注意以下几点事项以减少潜在风险因素影响最终效果呈现质量: - 务必遵循官方指导手册完成整个集成过程; - 定期更新第三方插件驱动程序直至最新稳定版发布为止; - 测试阶段最好单独隔离环境执行以便快速定位具体故障源头所在区域范围大小差异程度如何变化趋势走向等等细节特征表现形式特点规律总结归纳提炼升华形成理论依据支撑实践操作可行性研究探讨交流分享传播推广普及教育培养人才储备力量积蓄能量爆发潜力无限可能性探索发现创新创造价值贡献社会进步发展推动历史车轮滚滚向前永不停歇追求卓越成就非凡梦想成真美好未来展望憧憬期待满怀信心迎接挑战克服困难战胜挫折勇往直前无惧风雨一路高歌猛进势不可挡所向披靡战无不胜攻无不克百折不挠坚忍不拔顽强拼搏奋斗终生不留遗憾青春无悔热血沸腾激情燃烧岁月峥嵘光辉灿烂辉煌荣耀永恒铭刻史册流芳千古万代传颂! --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值