冲刺NOI2017 (22) 养猫 (线性规划方程转网络流)

题目大意

你养了一只猫,为了让它快乐地成长,你需要合理地安排它每天的作息时间。假设一天分为 n 个时刻,猫在每个时刻要么是吃东西,要么是睡觉。在第i个时刻,假如猫是去吃东西,那么它能获得愉悦值 ei ,假如是去睡觉,那么能获得的愉悦值为 si

猫要成长,不仅仅需要快乐,还需要健康的作息。经过研究,对于每一个连续的长度为 k 的作息时间,即所有的时刻区间[i,i+k1],1ink+1,猫都至少要有 min_sleep 的时刻用来睡觉, min_eat 的时刻用来吃东西,这样猫才能健康成长。

现在你想合理地安排一天中的这n个时刻,使得猫在能健康成长的前提下,获得尽量多的愉悦值。


题解

首先不考虑上述限制条件,假设猫的所有时刻都在吃东西,那么问题就转化为了从中选出一些时刻来睡觉,使其作息符合健康的要求,并使其愉悦值总和尽可能地高。

对于一个长度为 k 的区间,我们要选出的睡觉时刻总数一定要大于等于min_sleep且小于等于 kmin_sleep ,为了方便下面式子的书写,记 min_sleep mn ,记 kmin_sleep mx ,则对于任何一个长度为k的区间内被选出的睡觉时刻数量 num 都满足 mnnummx 。但是这种表达方式并不能列出线性规划方程,我们在下面会稍作变形。对于每一个时刻 i ,设xi表示时刻i是否被选为睡觉时刻,则有下列式子:

x11100000x21111000x31111110x41111110x50011110x60000110+a1b1+a2b2+a3b3=======mxmnmxmnmxmn0

上述式子的第1个第2个式子表示第一个长度为 k(k=4) 的区间,以此类推。式子中填入的1表示在这个式子中,此点是可选的。至于为什么在下面留了一行空公式,在后面的时候会解释。

上式为了满足线性规划方程的性质同时也易于处理,使用加或减一个不定取值的非负整数 ai bi ,代替式子中的不等号的使用。但是上面的式子仍然看不出什么性质,这时我们就需要将式子化简一下。

.

'''''''x11010000x21000100x31000001x41000001x50010001x60000101+a1a1b1+b1+a2a2b2+b2+a3a3b3+b3=======mxmnmxmxmnmnmxmxmnmnmxmn

这里简要描述一下化简式子的方式:式②’=式②-式①,式③’=式③-式②……以此类推。

化简之后,每一列 xi 都有且仅有一个1和一个-1,这样就可以转化为网络流了啊!(因为每一条边的流入流量等于其流出流量,此处只可意会不可言传)。这样的线性规划方程转化成网络流的具体方法为:先把每个式子当作一个结点来考虑。然后,对于等号左侧的式子的不为0的部分,值为正表示为一条出边,值为负则表示为一条入边。对于每一列 xi ,在值为1的式子和值为-1的式子之间连边;其余的部分在绝对值相等的两式上连边。对于等号右侧的式子,因为所有的值都是常量,所以流量都是确定的(都是常量的绝对值),所以值为正的部分从源点连到该公式,值为负的部分从该公式连到汇点。

这里给出第三个点(公式③’)的连边的例子,常量部分分别与S,T连边, ai,bi 由于都是取值是非负整数的变量,所以流量设为inf,表示可以在非负整数范围内随意取值,每选一条从 xi 列上产生的边就相当于把一个时刻从吃饭变成了睡觉,所以权值 vi 应为 sici ,又因为要求的是最大价值,所以取个相反数就可以咯。


这里写图片描述

总说线性规划转网络流是只可意会不可言传的?这里我稍微言传一下,因为这里面每一个时刻是否选用只能用一条边的形式表达出来,且一条边的流出量一定等于流入量,所以 xi 列只能化简到最后为有且仅有一个1个一个-1的形式。把每个等式作为结点的原因也很简单,就是利用了网络流中每个结点的流出量等于流入量这个性质,粗暴一点来说等式左侧的就是流出口,等式右侧的就是流入口,而也因为值有正有负,所以负值部分的式子就表示为“流入流出口”或“流出流入口”这样的形式了。


代码

#include <cstdio>
#include <iostream>
//#include <ctime>
#include <queue>
using namespace std;

const int maxn=int(2e3)+111;
int n,k,minc,mins;
int c[maxn], s[maxn];

void Read() {
    scanf("%d%d%d%d",&n,&k,&minc,&mins);
    register int i;
    for(i=1;i<=n;++i) scanf("%d",&c[i]);
    for(i=1;i<=n;++i) scanf("%d",&s[i]);
    return;
}

const int maxe=int(2e6)+111, inf=int(1e9)+7;
const long long INF=1ll<<60;
int S,T;
int id[maxn];
int head[maxn], tot=0;

struct Edge {
    int to,cap,cost,next;
    Edge() {}
    Edge(int y,int ca,int co,int nx):to(y),cap(ca),cost(co),next(nx) {}
}eage[maxe<<1];

inline void Add_eage(int x,int y,int cap,int cost) {
    eage[tot]=Edge(y,cap,cost,head[x]), head[x]=tot++;
    eage[tot]=Edge(x,0,-cost,head[y]), head[y]=tot++;
}

void Build() {
    int mx=k-minc,mn=mins,num=(n-k+1)<<1;
    S=0, T=num+2;
    register int i;
    for(i=S;i<=T;++i)
        head[i]=-1;

    //Constant on rhs
    Add_eage(S,1,mx,0);
    Add_eage(num+1,T,mn,0);
    for(i=2;i<=num;++i) {
        if(i&1) Add_eage(S,i,mx,0), Add_eage(i,T,mn,0);
        else Add_eage(S,i,mn,0), Add_eage(i,T,mx,0);
    }
    //Variable on lhs
    for(i=1;i<=num+1;i+=2) {
        if(i<=num) Add_eage(i,i+1,inf,0);
        if(i>1) Add_eage(i,i-1,inf,0);
    }
    //Keypoint on lhs
    for(i=1;i<=n;++i) {
        if(1+(i<<1)>num) Add_eage(i>k?1+((i-k)<<1):1,num+1,1,c[i]-s[i]);
        else Add_eage(i>k?1+((i-k)<<1):1,1+(i<<1),1,c[i]-s[i]);
        id[i]=tot-1;
    }


}

long long dis[maxn];
bool used[maxn], vis[maxn];
queue<int> que;

bool spfa() {
    while(que.size()) que.pop();
    register int i,u,v;
    for(i=0;i<maxn;++i) {
        dis[i]=INF;
        used[i]=false, vis[i]=false;
    }

    que.push(S);
    used[S]=true;
    dis[S]=0;

    while(que.size()) {
        u=que.front(); que.pop();
        used[u]=false;
        for(i=head[u];~i;i=eage[i].next) if(eage[i].cap && dis[eage[i].to]>dis[u]+eage[i].cost) {
            v=eage[i].to;
            dis[v]=dis[u]+eage[i].cost;
            if(!used[v]) {used[v]=true; que.push(v);}
        }
    }
    return dis[T]<INF;
}

long long ans=0;
int dfs(int u,int flow) {
    if(u==T || !flow) {
        ans+=dis[u]*flow;
        return flow;
    }
    vis[u]=true;
    int ret=0;
    register int i,v;
    for(i=head[u];~i;i=eage[i].next) if(eage[i].cap && !vis[eage[i].to] && dis[eage[i].to]==dis[u]+eage[i].cost) {
        v=eage[i].to;
        int newf=dfs(v,min(flow,eage[i].cap));
        eage[i].cap-=newf;
        eage[i^1].cap+=newf;
        ret+=newf;
        flow-=newf;
        if(!flow) break;
    }
    if(!ret) dis[u]=-1;
    return ret;
}

int MCMF() {
    int res=0; ans=0;
    while(spfa()) res+=dfs(S,inf);
    ans=-ans;
    return res;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("cat.in","r",stdin);
    freopen("cat.out","w",stdout);
#endif
//  double t1=clock();

    Read();
    Build();
    MCMF();

    register int i;
    for(i=1;i<=n;++i) ans+=c[i];

    cout<<ans<<endl;
    for(i=1;i<=n;++i) {
        if(eage[id[i]].cap) putchar('E');
        else putchar('S');
    }
    putchar('\n');

//  printf("%.3lfsec\n",(clock()-t1)/CLOCKS_PER_SEC);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
问题编号 问题名称 问题模型 化模型 1 飞行员配对方案问题 二分图最大匹配 网络最大 2 太空飞行计划问题 最大权闭合图 网络最小割 3 最小路径覆盖问题 有向无环图最小路径覆盖 网络最大 4 魔术球问题 有向无环图最小路径覆盖 网络最大 5 圆桌问题 二分图多重匹配 网络最大 6 最长递增子序列问题 最多不相交路径 网络最大 7 试题库问题 二分图多重匹配 网络最大 8 机器人路径规划问题 (未解决) 最小费用最大 9 方格取数问题 二分图点权最大独立集 网络最小割 10 餐巾计划问题 线性规划网络优化 最小费用最大 11 航空路线问题 最长不相交路径 最小费用最大 12 软件补丁问题 最小移代价 最短路径 13 星际移问题 网络判定 网络最大 14 孤岛营救问题 分层图最短路径 最短路径 15 汽车加油行驶问题 分层图最短路径 最短路径 16 数字梯形问题 最大权不相交路径 最小费用最大 17 运输问题 网络费用量 最小费用最大 18 分配问题 二分图最佳匹配 最小费用最大 19 负载平衡问题 最小代价供求 最小费用最大 20 深海机器人问题 线性规划网络优化 最小费用最大 21 最长k可重区间集问题 最大权不相交路径 最小费用最大 22 最长k可重线段集问题 最大权不相交路径 最小费用最大 23 火星探险问题 线性规划网络优化 最小费用最大 24 骑士共存问题 二分图最大独立集 网络最小割

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值