并不对劲的餐巾计划问题

三倍经验题!

********************题目略长**********************

题目描述

一个餐厅在相继的N天里,第i天需要ri块餐巾(i=l,2,…,N)。餐厅可以从三种途径获得餐巾。

(1)购买新的餐巾,每块需p分;

(2)把用过的餐巾送到快洗部,洗一块需m天,费用需f分(f<p)。如m=l时,第一天送到快洗部的餐巾第二天就可以使用了,送慢洗的情况也如此。

(3)把餐巾送到慢洗部,洗一块需n天(n>m),费用需s分(s<f)。

在每天结束时,餐厅必须决定多少块用过的餐巾送到快洗部,多少块送慢洗部。在每天开始时,餐厅必须决定是否购买新餐巾及多少,使洗好的和新购的餐巾之和满足当天的需求量Ri,并使N天总的费用最小

输入输出格式

输入格式:

输入文件共3行,第1行为总天数;第2行为每天所需的餐巾块数;第3行为每块餐巾的新购费用p,快洗所需天数m,快洗所需费用f,慢洗所需天数n,慢洗所需费用s。

输出格式:

输出文件共1行为最小的费用。

输入输出样例

输入样例:
3
3 2 4
10 1 6 2 3
输出样例:
64

说明

N<=2000

ri<=10000000

p,f,s<=10000

时限4s

*************在网络流24题中算是神奇的一题(其实并不一定是网络流)************

网络流24题中,存在各种只是来让人练写模板熟练度的题。不过这题的建图方式算是一股清流,让人很不好想,但想到了也很不好证明,不过证明出来后真的很好写。

对于送到快洗部、慢洗部、直接购买都很好想,拆点后直接连就行了。也就是把一天拆成上午和下午两个点,上午可以接受洗完的餐巾、买新餐巾,下午可以把餐巾送去洗。也就是说,上午的“流”的意义是干净的餐巾,下午的“流”的意义是脏的餐巾。

让第i天“经过”汇点的流为ri显然是行不通的。可以把“拿去用ri个餐巾”拆成两个动作:上午把ri个干净的餐巾给汇点,下午接收从源点来的ri个脏餐巾。

还要注意的是,餐厅里可以攒干净的餐巾,所以第i天上午要连一条边到第i+1天上午。

以上过程其实可以用有上下界网络流来思考,但并不对劲的人并不想这么做。

#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define ll long long
#define maxn 4010
#define maxm 2000010
using namespace std;
const ll inf=0x7fffffff;
ll fir[maxn],nxt[maxm],v[maxm],fl[maxm],w[maxm],cnt;
ll mincost,n,m,p,f,s,N,dis[maxn];
bool vis[maxn];
ll read()
{
    ll x=0,f=1;
    char ch=getchar();
    while(isdigit(ch)==0 && ch!='-')
        ch=getchar();
    if(ch=='-')f=-1,ch=getchar();
    while(isdigit(ch))x=x*10+ch-'0',
        ch=getchar();
    return x*f;
}
void addedge(ll u1,ll v1,ll fl1,ll w1)
{
    w[cnt]=w1,v[cnt]=v1,fl[cnt]=fl1,nxt[cnt]=fir[u1],fir[u1]=cnt++;
    w[cnt]=-w1,v[cnt]=u1,fl[cnt]=0,nxt[cnt]=fir[v1],fir[v1]=cnt++;
}
ll spfa() 
{
    memset(dis,-1,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[N*2+1]=0;
    deque<ll >q;
    q.push_back(N*2+1);
    while(!q.empty())
    {
        ll u=q.front();q.pop_front();
        for(ll k=fir[u];k!=-1;k=nxt[k])
        {
            ll vv=v[k];
            if(fl[k^1]>0)
            {
                if(dis[vv]>dis[u]-w[k] 
                || dis[vv]==-1)
                {
                    dis[vv]=dis[u]-w[k];
                    if(vis[vv]==0)    
                    {
                        if(q.empty()==0&&
                        dis[q.front()]>dis[vv])
                            q.push_front(vv);
                        else
                            q.push_back(vv);
                    }    
                    vis[vv]=1;
                }
            }
        }
        vis[u]=0;
    }
    return dis[0];
}
ll getfl(ll u,ll nowflow)
{
    vis[u]=1;
    if(u==N*2+1)return nowflow;
    ll tmp,sum=0;
    for(ll k=fir[u];k!=-1;k=nxt[k])
    {
        if(nowflow<=0)break;
        ll vv=v[k];
        if(vis[vv]==0 && fl[k]>0 
        && dis[vv]+w[k]==dis[u]&&
        (tmp=getfl(vv,min(nowflow,fl[k])))>0)
        {
            fl[k]-=tmp;
            fl[k^1]+=tmp;
            nowflow-=tmp;
            mincost+=tmp*w[k];
            sum+=tmp;
        }
     } 
    return sum;
}
int main()
{
    memset(fir,-1,sizeof(fir));
    N=read();
    for(ll i=1;i<=N;i++)
    {
        ll r=read();
        addedge(i,N*2+1,r,0);
        addedge(0,i+N,r,0);
    }
    p=read(),m=read(),f=read(),n=read(),s=read();
    for(ll i=1;i<=N;i++)
    {
        addedge(0,i,inf,p);
        if(i+m<=N)addedge(i+N,i+m,inf,f);
        if(i+n<=N)addedge(i+N,i+n,inf,s);
        if(i+1<=N)addedge(i,i+1,inf,0);
    }
    while(spfa()!=-1)
    {
        memset(vis,0,sizeof(vis));
        getfl(0,0x7fffffff);
    }
    cout<<mincost;
    return 0;
}
并不对劲的费用流

ex1(BJWC):如果n<=2 * 10^5,ri<=100该怎么做?

其实可以三分购买的毛巾总数,然后贪心地算出是否可行。

假设购买的毛巾总数是给定的,那么这道题就变得简单多了。在第x天将毛巾送去洗,到第x+m或x+n天才能用,这件事可以看成在第x天毛巾不够用时,花s或f乘坐时光机回到第x-n或x-m天,将毛巾变成干净的带回来。对于前面的若干天,新买的毛巾不用白不用,尽量把新买的用完。新毛巾不够用时考虑回到之前的某一天。这时肯定回到x-m或再之前的时间更划算。因为x-m及以前可以用慢洗,花费少。如果x-m天及之前没有脏毛巾了,才去第x-n到x-m天之间。这时最好取离现在更近的。这样可以让离现在更远的时间,也就是离第x-m天更近的时间,更有机会慢洗。这个过程可以用双端队列维护。

根据这个策略,对于每一个购买的毛巾总数c,都有唯一确定的总费用与之对应。因此定义f(c)为总共购买c块毛巾的费用。注意c的取值范围应该是大于某个数,因为新毛巾总数太少的时候有些天会没毛巾可用。

三分法要保证单峰。感性地想,c较小时要频繁地使用快洗,所以花在洗毛巾上的费用会很大。而c较大时购买毛巾的费用会很大。

理性地想,并不对劲的人并不理性。

设ri之和为R,这样就可以做到时间复杂度O(n log R)了呢。

 

ex2:在ex1的条件下,若快洗店更便宜,该怎么办?

那就别慢洗了。

 

是贪心能解所有网络流问题,还是网络流能解所有贪心问题呢(就是时间复杂度……)?

posted @ 2018-01-19 16:05  echo6342  阅读(311)  评论(0编辑  收藏  举报