SCOI2014方伯伯运椰子 (分数规划+SPFA)

本文介绍了一个关于椰子园物流网络优化的问题,旨在通过调整网络中的道路容量来降低运输成本,确保物流网络在高运输量下的经济性。

题目描述

四川的方伯伯为了致富,决定引进海南的椰子树。方伯伯的椰子园十分现代化,椰子园中有一套独特的交通系统。

现在用点来表示交通节点,边来表示道路。这样,方伯伯的椰子园就可以看作一个有 n + 2 个交通节点,m条边的有向无环图。n +1 号点为入口,n +2 号点为出口。每条道路都有 6 个参数,ui,vi,ai,bi,ci,di,分别表示,该道路从 ui 号点通向 vi 号点,将它的容量压缩一次要 ai 的花费,容量扩大一次要 bi 的花费,该条道路当前的运输容量上限为 ci,并且每单位运输量通过该道路要 di 的费用。

在这个交通网络中,只有一条道路与起点相连。因为弄坏了这条道路就会导致整个交通网络瘫痪,聪明的方伯伯决定绝不对这条道路进行调整,也就是说,现在除了这条道路之外,对其余道路都可以进行调整。

有两种调整方式:

选择一条道路,将其进行一次压缩,这条道路的容量会下降 1 单位。
选择一条道路,将其进行一次扩容,这条道路的容量会上升 1 单位。

一条道路可以被多次调整。

由于很久以前,方伯伯就请过一个工程师,对这个交通网络进行过一次大的优化调整。所以现在所有的道路都被完全的利用起来了,即每条道路的负荷都是满的(每条道路的流量等于其容量)。

但方伯伯一想到自己的海南椰子会大丰收,就十分担心巨大的运输量下,会导致过多的花费。因此,方伯伯决定至少进行一次调整,调整之后,必须要保持每条道路满负荷,且总交通量不会减少。

设调整后的总费用是 Y,调整之前的总费用是 X。现在方伯伯想知道,最优调整比率是多少,即假设他进行了 k 次调整,(X - Y)/k最大能是多少?

注:总费用 = 交通网络的运输花费 + 调整的花费
输入输出格式
输入格式:

第一行包含二个整数N,M接下来M行代表M条边,表示这个交通网络每行六个整数,表示Ui,Vi,Ai,Bi,Ci,Di接下来一行包含一条边,表示连接起点的边

输出格式:

一个浮点数,保留二位小数。表示答案,数据保证答案大于0

分析:
1.因为本题要求的是分数的最大值,所以要用到分数规划
2.因为与源点相连的只有一条边,所以这个图的流量是守恒的(总量不会变化)
3.那么压缩相当于退流,扩容相当于增广,增广相当于加了一条。
4.扩容1的费用为b+d,压缩1的费用为a-d
5.化一下给的式子:

xyk>mid

xykmid>0

yx+kmid<0

y-x即是扩容的费用加上压缩的费用。由于最后要除上操作总数k,因此对于相同的边操作多次是没有意义的。又因为存在mid的影响,所以每次加上mid再判断是否有负权环即可。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int maxn=5010;
const int maxm=3010;
const int INF=1e9;
int to[maxm*2],Next[maxm*2],w[maxm*2],Begin[maxn],e;
int n,m;
double dis[maxn];
void add(int x,int y,int z){
    to[++e]=y;
    Next[e]=Begin[x];
    Begin[x]=e;
    w[e]=z;
}
int inq[maxn],cnt[maxn];
int s,t;
bool SPFA(double add){
    memset(inq,0,sizeof(inq));
    memset(cnt,0,sizeof(cnt));
    for(int i=1;i<=n;i++) dis[i]=INF*1.0;
    queue<int>q;
    q.push(s);inq[s]=1;cnt[s]++;
    dis[s]=0;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=Begin[u];i;i=Next[i]){
            int v=to[i];
            if(cnt[v]>n) return true;
            double length=w[i]+add;
            if(dis[v]>dis[u]+length){
                dis[v]=dis[u]+length;
                if(!inq[v]) q.push(v),cnt[v]++;
            }
        }
    }
    return false;
}
int main(){
    scanf("%d%d",&n,&m);
    double L=0,R=0;
    s=n+1,t=n+2;
    n+=2;
    for(int i=1;i<=m;i++){
        int u,v,a,b,c,d;
        scanf("%d%d%d%d%d%d",&u,&v,&a,&b,&c,&d);
        if(c) add(v,u,a-d);
        add(u,v,b+d);
        if(a-d<0) R+=d-a;
    }
    double ans=0;
    while(R-L>=1e-3){
        double mid=(L+R)/2;
        if(SPFA(mid)){
            ans=mid;L=mid;
        }else R=mid;
    }
    printf("%.2f",ans);
    return 0;
}

^_^

<think>嗯,用户想解决石子合并问题的最小代价算法实现,还提到了L、R、K进制以及数学计算。首先,我得回忆一下石子合并问题的基本概念。这个问题通常是动态规划的经典例题,目标是将一堆石子合并成一堆,每次合并相邻的两堆,代价是两堆石子的总和,求最小总代价。不过用户提到的L、R、K进制可能是指合并的时候可以合并L到R堆,而K进制可能和计算式有关?这点需要进一步确认。 首先,我应该先明确用户的问题是否和传统的石子合并问题不同。传统的是每次合并相邻的两堆,但用户可能扩展了合并的堆数范围,比如每次可以合并L到R堆之间的任意堆数,或者每次必须合并恰好K堆?这可能影响动态规划的状态转移程。 另外,用户提到的K进制可能涉及到数学计算中的进制转换,或者是在代价计算时采用不同的进制表示?比如合并后的数值是否以K进制形式处理?或者合并的代价计算式涉及K进制算?这部分需要仔细分析。 接下来,我需要回顾传统的石子合并的动态规划解法。通常,状态定义为dp[i][j],表示合并第i到j堆石子的最小代价。转移程是dp[i][j] = min(dp[i][k] + dp[k+1][j] + sum(i,j)),其中k在i到j-1之间。而sum(i,j)可以通过前缀和数组来快速计算。 如果用户的问题允许每次合并L到R堆,那么状态转移可能需要调整。例如,在合并i到j堆时,需要考虑将i到k合并为一堆,然后剩下的k+1到j可能需要满足某种条件。或者,可能每次合并必须合并连续的K堆,这时候状态的定义可能需要更多的维度。 另外,如果涉及到不同的进制,比如合并后的数值转换为K进制再进行计算,这可能会影响sum的计算式。例如,合并两堆石子的总重量如果是a和b,那么在K进制下可能需要用a + b的K进制表示后的值作为代价?不过这种情况下的问题可能比较复杂,需要明确用户的具体需求。 可能的误解是用户可能混淆了石子合并的不同变体,比如环形石子合并或者每次合并多堆的情况。这时候需要进一步的信息,但由于用户的问题描述较为简略,可能需要基于常规问题给出解答,并指出可能的扩展向。 考虑到用户引用了站内的两个引用,一个是关于最短路算法,另一个是关于货仓选址的算法。货仓选址涉及中位数的应用,而石子合并的动态规划可能和区间DP相关。或许用户希望将类似中位数的思路应用到石子合并中,但传统法并非如此,可能需要结合其他数学法。 此外,用户提到的L、R、K进制可能是指合并时的条件,比如每次合并的堆数必须介于L和R之间,或者每次合并后的数值以K进制处理。例如,合并后的代价不是简单的相加,而是按照K进制进位,这可能改变状态转移时的计算式。 假设用户的问题是一个变种,比如每次可以合并L到R堆石子,那么动态规划的状态可能需要调整。例如,状态dp[i][j]表示合并i到j堆的最小代价,但转移时需要枚举分割点k,并且确保合并的堆数在L到R之间。或者可能需要合并为K堆,进而形成不同的子结构。 此时,我需要确定用户的问题具体是哪一种变体。但基于常规的石子合并问题,我会先给出标准的动态规划解法,然后讨论可能的扩展,比如合并堆数限制、不同进制的影响等。 总结步骤: 1. 确定石子合并问题的标准动态规划解法,使用区间DP。 2. 解释状态转移程和前缀和的应用。 3. 针对可能的L、R、K参数进行扩展讨论。 4. 如果用户的问题涉及数学计算或进制转换,分析其对算法的影响。 5. 提供代码实现示例,并说明可能的调整点。 同时,用户可能需要了解如何优化算法的时间复杂度,例如使用四边形不等式优化将复杂度从O(n^3)降低到O(n^2)等。不过这可能超出基础实现的范畴,但可以作为扩展点提及。 现在,需要确保回答结构清晰,分步骤说明,并引用相关的参考资料中的内容,比如货仓选址中的排序和中位数思路是否适用,但可能不直接相关。不过,引用中的动态规划思想可能相关。</think>### 石子合并问题的最小代价算法实现 #### 问题分析 石子合并问题要求将 $n$ 堆石子合并为一堆,**每次只能合并相邻的堆**,合并代价为两堆石子数量之和,最终目标是求最小总代价。该问题属于**区间动态规划**的经典应用[^1]。 #### 标准动态规划解法 1. **状态定义** 定义 $dp[i][j]$ 为合并第 $i$ 到第 $j$ 堆石子的最小代价。 2. **前缀和优化** 使用前缀和数组 $s[]$ 快速计算区间和:$s[i] = s[i-1] + a[i]$,区间 $[i,j]$ 的和为 $s[j] - s[i-1]$。 3. **状态转移程** $$dp[i][j] = \min_{k=i}^{j-1} \left( dp[i][k] + dp[k+1][j] \right) + (s[j] - s[i-1])$$ 4. **复杂度分析** 时间复杂度为 $O(n^3)$,空间复杂度为 $O(n^2)$。 #### 代码实现(标准版) ```python def merge_stones(a): n = len(a) s = [0] * (n + 1) for i in range(1, n+1): s[i] = s[i-1] + a[i-1] dp = [[0]*n for _ in range(n)] for length in range(2, n+1): # 合并区间长度 for i in range(n - length + 1): j = i + length - 1 dp[i][j] = float('inf') for k in range(i, j): # 分割点 cost = dp[i][k] + dp[k+1][j] + s[j+1] - s[i] if cost < dp[i][j]: dp[i][j] = cost return dp[0][n-1] ``` #### 扩展讨论(L/R/K相关) 1. **合并堆数限制** - **L/R限制**:若每次必须合并 $[L, R]$ 堆,需重新设计状态转移逻辑。例如当合并 $k$ 堆时,可能需要多维状态表示。 - **K进制计算**:若合并代价按K进制计算(如进位处理),需修改代价计算公式。例如合并后的值为 $(a + b) \% K$,进位值为 $(a + b) // K$,需在状态中记录进位影响。 2. **优化法** - **四边形不等式优化**:可将时间复杂度优化至 $O(n^2)$[^2]。 - **环形扩展**:将数组扩展为 $2n$ 长度处理环形问题。 #### 数学计算要点 - **贪心策略失效性**:直接选择最小两堆合并的贪心策略无法保证全局最优。 - **区间和累加特性**:最终代价本质是所有区间和的累加,与合并顺序密切相关。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值