2018.10.10【HDU4322】Candy(最大费用最大流)(建图)

传送门


解析:

隐藏极深的一个匹配问题。其实就是将糖果和小朋友匹配,问能否满足所有小朋友的需要。

思路:

看出来是一个匹配问题,那就直接考虑网络流。

首先先考虑 k = 2 k=2 k=2怎么做

显然当 k = 2 k=2 k=2时,我们尽量用小朋友喜欢的糖去提升他的快乐值,最后每个小朋友剩的快乐值就只有几种情况,要么差的远,要么刚好满足,要么还剩1。

这个可以直接用最大流实现,我们对每个小朋友向汇点连边,容量为 ⌊ b i 2 ⌋ \lfloor\frac{b_i}{2}\rfloor 2bi,源点向每个糖连边,容量为 1 1 1,表示这种糖有一颗。然后我们将每颗糖与喜欢它的小朋友连边,容量为 1 1 1

目前我们做出来最大流的两倍就是小朋友喜欢的糖能够带给他们的最大快乐值总和。
然后我解释一下以上三种边最后流量的意义:
1、小朋友向汇点连边:最终每个小朋友收获了多少颗他自己喜爱的糖。
2、源点向糖连边:最终这颗糖能不能给到一个喜爱它的小朋友。
3、糖向小朋友连边:最终这颗糖到了哪位小朋友的手里。

显然以上三种边各自的流量和相等,且均等于最大流。

那么我们的最大流 × 2 \times 2 ×2就是小朋友们喜欢的糖能够带给他们多少快乐值,而最大流本身的数值就是有多少糖给到了喜欢它的小朋友。

剩下小朋友快乐值的差值就是他们需要从不喜欢的糖里面获得多少快乐值。
显然我们已经知道有多少他们喜欢的糖被给出了,那么剩下的糖的数量也是知道的,比较一下就好了。

考虑怎么拓展到 k k k比较大的情况

我知道有很多人可能和我一样,粗暴地按照上面 k = 2 k=2 k=2的方法建了图跑最大流,不过显然错了,不然估计也不会来找题解。

为什么直接跑最大流是错的?
我们考虑一下当 k k k大于2的时候 b i % k b_i\%k bi%k有可能大于 2 2 2,比如 b i % k = 2 b_i\%k=2 bi%k=2
那么这时候对于第 i i i个小朋友显然直接再给他一块他喜欢的糖比给他两块它不喜欢的糖更划算。

如果还是无法理解的话,看看这个例子:
只有一个小朋友,需要快乐值 11 11 11,有 4 4 4块糖,喜欢的每块的快乐值是 3 3 3,他每块都喜欢。
按照上面的方法跑最大流会建一条容量为 ⌊ 11 3 ⌋ = 3 \lfloor\frac{11}{3}\rfloor=3 311=3的边,那么最终的容量就是 3 3 3,获得快乐值就是 3 × 3 = 9 3\times 3=9 3×3=9,还剩一块。。。然而我们并没有将它算进小朋友喜欢的里面。
不要给博主扯什么特判,数据大了你特判的正确性都没有办法保证,亲测。

那么怎么做呢?
回到 k = 2 k=2 k=2的情况,我们在第一种情况建的边中的流量和就是最大流, × 2 \times 2 ×2就是小朋友获得的快乐值。

发现什么没有?为什么我们最大流要 × 2 \times 2 ×2
因为每块糖给喜欢它的小朋友能够获得的快乐值是 2 2 2

那么我们在 k > 2 k>2 k>2的情况作如下处理:
1、还是直接连向汇点 ⌊ b i k ⌋ \lfloor\frac{b_i}{k}\rfloor kbi的容量边,费用为 k k k
2、糖的所有边与上面的处理方法相同,费用为 0 0 0
3、对于 b i % k > 1 b_i\%k>1 bi%k>1的情况,再将这个小朋友连向汇点一条容量为 1 1 1,费用为 b i % k b_i\%k bi%k的边

对于3中的处理,我再解释一下,当 b i % k &lt; = 1 b_i\%k&lt;=1 bi%k<=1的时候,一块不喜欢的糖和一块喜欢的糖的效果是一样的,只有当 b i % k &gt; 1 b_i\%k&gt;1 bi%k>1的时候,一块喜欢的糖能够比不喜欢的糖更划算,此时最后一块糖能够弥补的快乐值就只有 b i % k b_i\%k bi%k

那么这样建出来的图直接跑最大费用最大流,费用就是我们能够让小朋友通过他们喜欢的糖获得的最大快乐值,流量就是我们消耗了多少颗糖。

最后的比较和上面的情况相同。

其实最大费用怎么求。。。
就是把费用取相反数,跑最小费用就行了。。。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline
int getint(){
    re int num;
    re char c;
    while(!isdigit(c=gc()));num=c^48;
    while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
    return num;
}

cs int N=31,M=2000006,INF=0x3f3f3f3f;
int last[N],nxt[M<<1],to[M<<1],ecnt=1;
int cap[M<<1],cost[M<<1];
inline
void addedge(int u,int v,int val,int co){
    nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v,cap[ecnt]=val,cost[ecnt]=co;
    nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u,cap[ecnt]=0  ,cost[ecnt]=-co;
}

int cur[N],dist[N];
bool vis[N];
queue<int>q;
inline
bool SPFA(cs int &ss,cs int &tt){
    memset(dist,0x3f,sizeof dist);
    memset(vis,0,sizeof vis);
    memcpy(cur,last,sizeof last);
    dist[ss]=0;
    q.push(ss);
    vis[ss]=true;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        vis[u]=false;
        for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
            if(cap[e]&&dist[v]>dist[u]+cost[e]){
                dist[v]=dist[u]+cost[e];
                if(!vis[v])vis[v]=true,q.push(v);
            }
        }
    }
    return dist[tt]<100000;
}

inline
int dfs(cs int &u,cs int &flow,cs int &tt,int &co){
    if(u==tt){
        co+=flow*dist[tt];
        return flow;
    }
    vis[u]=true;
    int ans=0;
    for(int &e=cur[u],v=to[e];e;v=to[e=nxt[e]]){
        if(!vis[v]&&cap[e]&&dist[u]+cost[e]==dist[v]){
            int delta=dfs(v,min(flow-ans,cap[e]),tt,co);
            if(delta){
                cap[e]-=delta;
                cap[e^1]+=delta;
                ans+=delta;
                if(ans==flow)return vis[u]=false,flow;
            }
        }
    }
    vis[u]=false;
    return ans;
}

inline
pair<int,int> maxflow(cs int &ss,cs int &tt){
    int ans=0,co=0;
    while(SPFA(ss,tt))ans+=dfs(ss,INF,tt,co);
    return make_pair(ans,-co); 
}

inline void init(){
    ecnt=1;
    memset(last,0,sizeof last);
}

int t;
int n,m,k;
int S=0,T=30;
int tot;
signed main(){
    t=getint();
    for(int re tt=1;tt<=t;++tt){
        printf("Case #%d: ",tt);
        init();
        tot=0;
        n=getint(),m=getint(),k=getint();
		for(int re i=1;i<=n;++i)addedge(S,i,1,0);
        for(int re i=1;i<=m;++i){
            int b=getint();tot+=b;
            if(b>=k)addedge(i+n,T,b/k,-k);
            if(b%k>1)addedge(i+n,T,1,-b%k);
        }
        for(int re i=1;i<=m;++i)
        for(int re j=1;j<=n;++j){
            int flag=getint();
            if(flag){
                addedge(j,i+n,1,0);
            }
        }
        pair<int,int> tmp=maxflow(S,T);
        if(n-tmp.first>=tot-tmp.second)puts("YES");
        else puts("NO");
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值