bzoj 2424 [HAOI2010]订货 费用流/动态规划

Description

某公司估计市场在第i个月对某产品的需求量为Ui,已知在第i月该产品的订货单价为di,上个月月底未销完的单位产品要付存贮费用m,假定第一月月初的库存量为零,第n月月底的库存量也为零,问如何安排这n个月订购计划,才能使成本最低?每月月初订购,订购后产品立即到货,进库并供应市场,于当月被售掉则不必付存贮费。假设仓库容量为S。
Input

第1行:n, m, S (0<=n<=50, 0<=m<=10, 0<=S<=10000)
第2行:U1 , U2 , … , Ui , … , Un (0<=Ui<=10000)
第3行:d1 , d2 , …, di , … , dn (0<=di<=100)
Output

只有1行,一个整数,代表最低成本

Sample Input

3 1 1000

2 4 8

1 2 4

Sample Output

34
HINT


传送门
一天买一定量以上的货品,累计到下一天单位价值m……
非常熟悉的一个东西(然而我并没有一眼看出来)
这个货品的数目可以抽象为“流”,
假设有一个源点,不断发出流,流量为f,
发到一个点x之后,产生了 D[x]f 的价值,
然后要满足 f>=U[x] ,假如有剩余,则 fU[x] 的流会流向下一个点,
并且产生了 (fU[x])m 的价值。
那么构图就很容易了,建立一个超级源,连接向每一个点,
这里的点就是指1~n的每天,
然后连接x点单位流量价值D[x],容量
第x天和第(x+1)天连接,单位流量价值m,容量S(最多仓库可以积累S),
最后1~n天和汇点连接,价值是0,但容量是U[x],
为什么这么设置容量呢?我们知道U[x]都是要满足的,
所以这样的话,跑最小费用最大流就可以直接满足条件了。



然后说说动态规划的做法。
我第一眼还是想到了dp……但是思路很不清晰导致写不出来,
于是就重新想了一遍……
最后还是大佬理了一下思路结果豁然开朗
(orz……)
其实dp也是可以的,而且很简单,dp[i][j]表示前i天后仓库剩余j货品的最小花费。
但是事实上有一些细节,那就规定:
dp[i][j]表示前i天,仓库剩余货品减去U[i]后还剩j,并且付掉了j货品保存费用的最小花费。
假设考虑“减去U[i]”,“付掉保存j货品费用”这些都去掉的最小答案是g[i][j]
那么:
g[i][j]=min{f[i1][k]+D[i](jk),k<=j}
1<=i<=n,0<=j<=S+U[i]
然后其实g[i][j]和f[i][j]之间是可以推导的。
f[i][j]=g[i][jU[i]]+(jU[i])m
时间复杂度O(n*S^2)。
优化很简单,观察g[i][j]的式子,
分离之后就可以直接(似乎单调栈)维护了。


代码是费用流的……dp不想码了QAQ
2333跑得比dp还快

#include<bits/stdc++.h>
using namespace std;
const int 
    N=55,
    M=220,
    inf=2000000000;
int n,m,S,Ecnt;
int dis[N],pre[N],U[N],D[N];
bool vis[N];
struct Edge{
    int next,from,to,C,cost;
}E[M<<1];int head[N];
void add(int u,int v,int C,int x){
    E[Ecnt].next=head[u];
    E[Ecnt].from=u,E[Ecnt].to=v;
    E[Ecnt].C=C,E[Ecnt].cost=x;
    head[u]=Ecnt++;
}
int SPFA(int s,int t){
    memset(pre,0,sizeof(pre));
    memset(dis,100,sizeof(dis));
    int tinf=dis[1];
    queue<int>Q;
    while (!Q.empty()) Q.pop();
    Q.push(s),dis[s]=0;
    while (!Q.empty()){
        int u=Q.front();Q.pop();
        vis[u]=0;
        for (int i=head[u];~i;i=E[i].next)
            if (E[i].C){
                int v=E[i].to;
                if (dis[u]+E[i].cost<dis[v]){
                    dis[v]=dis[u]+E[i].cost;
                    pre[v]=i;
                    if (!vis[v]) vis[v]=1,Q.push(v);
                }
            }
    }
    if (dis[t]<tinf) return 1;
    return 0;
}
int MCMF(int s,int t){
    int ans=0;
    while (SPFA(s,t)){
        int f=inf;
        for (int i=t;i!=s;i=E[pre[i]].from)
            f=min(f,E[pre[i]].C);
        for (int i=t;i!=s;i=E[pre[i]].from)
            E[pre[i]].C-=f,E[pre[i]^1].C+=f;
        ans+=dis[t]*f;
    }
    return ans;
}
int main(){
    scanf("%d%d%d",&n,&m,&S);
    for (int i=1;i<=n;i++) scanf("%d",&U[i]);
    for (int i=1;i<=n;i++) scanf("%d",&D[i]);
    int source=n+1,sink=n+2;
    memset(head,255,sizeof(head));
    for (int i=1;i<=n;i++)
        add(source,i,inf,D[i]),add(i,source,0,-D[i]),
        add(i,sink,U[i],0),add(sink,i,0,0);
    for (int i=1;i<n;i++)
        add(i,i+1,S,m),add(i+1,i,0,-m);
    printf("%d\n",MCMF(source,sink));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值