Luogu P1251 餐巾计划问题 题解 [ 紫 ] [ 费用流 ] [ 图论建模 ]

餐巾计划问题:在李辉课上想出的建模题,感觉这个建模有点厉害。

建模

我们定义 \((u,v,c,w)\) 指从 \(u\)\(v\),流量为 \(c\),费用为 \(w\) 的一条边。

首先题目有这几个限制:

  • 每天的纸巾必须有 \(r_i\) 个。
  • 每天可以拿一部分纸巾到两种渠道去洗,分别消耗时间和金钱。
  • 每天还可以直接买新的纸巾。
  • 每天的纸巾可以继承。

首先处理第一个限制,题目要求我们每天必须有 \(r\) 个纸巾,根据最小费用最大流的性质(一定要满足流最大的情况下再满足最小费用),我们可以把这个必须满足的条件设为每天通往汇点的流量。这样就保证了每天必有 \(r\) 个纸巾流向汇点,也就保证了必须要有 \(r\) 个纸巾。

而又因为每天的两个操作是相对独立的,所以我们考虑对操作进行拆点:一个专门处理干净的纸巾 \(i_1\),一个专门处理脏的纸巾 \(i_2\),相当于一个储存纸巾的地方,遇到需要的流就直接供应上就可以了。

这些纸巾都流向汇点了,那么我们如何让他们再次回到纸巾循环系统中呢?我们只需要从源点连一条 \((S,i_2,r_i,0)\) 的边就可以了。这等效于把流向汇点的纸巾送回循环里。

继承操作也是简单的,我们要么在 \(i_1\) 之间向下一个 \(i_1\) 继承,要么在 \(i_2\) 之间向下一个 \(i_2\) 继承。流量设为 \(inf\),费用为 \(0\)

那么洗纸巾的操作就更加自然了,我们连 \((i_2,(i+x)_2,inf,p)\) 的边即可,其中 \(p\) 是洗纸巾的代价。注意判断 \(i+x\) 是否小于等于 \(n\)

买纸巾就直接在一开始买完就可以,连 \((S,1,inf,p)\) 的边就行。或者给每个 \(i_1\) 都连一个买纸巾的边也行。

于是建模就做完了。直接跑最小费用最大流即可,答案为最小的费用。时间复杂度 \(O(n^2m)\)

代码

/*
S -> i getin
i -> T getout
i,1 -> i+b,0
i,1 -> i+d,0
i,1 -> i+1,1
S -> i,0 buy
*/

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=5005,M=50005;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,s,t;
ll r[N];
int h[N],cur[N],idx=1;
struct Edge{
    int v,ne;
    ll c,w;
}e[M];
void add(int u,int v,ll c,ll w)
{
    e[++idx]={v,h[u],c,w};
    h[u]=idx;
}
void addeg(int u,int v,ll c,ll w)
{
    add(u,v,c,w);
    add(v,u,0,-w);
}
int getid(int u,int lv)
{
    return (u+lv*n);
}
ll cost=0,dis[N];
bitset<N>vis;
bool SPFA()
{
    memset(dis,0x3f,sizeof(dis));
    vis.reset();
    queue<int>q;
    q.push(s);
    vis[s]=1;
    dis[s]=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=h[u];i;i=e[i].ne)
        {
            int v=e[i].v;ll c=e[i].c,w=e[i].w;
            if(dis[v]>dis[u]+w&&c)
            {
                dis[v]=dis[u]+w;
                if(vis[v]==0)
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    return (dis[t]<(inf/2));
}
ll dfs(int u,ll mf)
{
    if(u==t)return mf;
    ll sm=0;
    vis[u]=1;
    for(int i=cur[u];i;i=e[i].ne)
    {
        int v=e[i].v;ll c=e[i].c,w=e[i].w;
        cur[u]=i;
        if(dis[v]==dis[u]+w&&vis[v]==0&&c)
        {
            ll res=dfs(v,min(mf,c));
            cost+=w*res;
            e[i].c-=res;
            e[i^1].c+=res;
            mf-=res;
            sm+=res;
            if(mf==0)break;
        }
    }
    if(sm==0)dis[u]=inf;
    return sm;
}
void dinic()
{
    while(SPFA())
    {
        memcpy(cur,h,sizeof(h));
        vis.reset();
        dfs(s,inf);
    }
}
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    ll a,b,c,d,e;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>r[i];
    cin>>a>>b>>c>>d>>e;
    s=2*n+1,t=2*n+2;
    addeg(s,getid(1,0),inf,a);
    for(int i=1;i<=n;i++)
    {
        int u=s,v=getid(i,1);
        addeg(u,v,r[i],0);
        u=getid(i,0),v=t;
        addeg(u,v,r[i],0);
        if(i+b<=n)
        {
            u=getid(i,1),v=getid(i+b,0);
            addeg(u,v,inf,c);
        }
        if(i+d<=n)
        {
            u=getid(i,1),v=getid(i+d,0);
            addeg(u,v,inf,e);
        }
        if(i<n)
        {
            u=getid(i,0),v=getid(i+1,0);
            addeg(u,v,inf,0);
        }
    }
    dinic();
    cout<<cost;
    return 0;
}
posted @ 2025-03-20 01:35  KS_Fszha  阅读(13)  评论(0)    收藏  举报