HDU 4322 Candy (最大费用最大流)经典

Candy

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1767    Accepted Submission(s): 478


Problem Description
There are N candies and M kids, the teacher will give this N candies to the M kids. The i-th kids for the j-th candy has a preference for like[i][j], if he like the sugar, like[i][j] = 1, otherwise like[i][j] = 0. If the i-th kids get the candy which he like he will get K glad value. If he or she do not like it. He will get only one glad value. We know that the i-th kids will feel happy if he the sum of glad values is equal or greater than B[i]. Can you tell me whether reasonable allocate this N candies, make every kid feel happy.
 

Input
The Input consists of several cases .The first line contains a single integer t .the number of test cases.
For each case starts with a line containing three integers N, M, K (1<=N<=13, 1<=M<=13, 2<=K<=10)
The next line contains M numbers which is B[i](0<=B[i]<=1000). Separated by a single space.
Then there are M*N like[i][j] , if the i-th kids like the j-th sugar like[i][j]=1 ,or like[i][j]=0.
 

Output
If there have a reasonable allocate make every kid feel happy, output "YES", or "NO".
 

Sample Input
  
  
2 3 2 2 2 2 0 0 0 0 0 1 3 2 2 2 2 0 0 0 0 0 0
 

Sample Output
  
  
Case #1: YES Case #2: NO
Hint
Give the first and second candy to the first kid. Give the third candy to the second kid. This allocate make all kids happy.
 

Author
BJTU
 

Source
 
题意:有N颗糖果(编号1~N)分给M个小孩(编号1~M),如果like[ i ][ j ]==1说明第 i 个小孩喜欢吃第 j 个糖果,如果他分得了第 j 个糖果则会得到价值 K ,如果他分到不喜欢吃的糖果则得到价值 1 。问能否满足每个小孩 i 得到价值>=B[ i ] 。如果能满足输出YES,否则NO。
解题:首先每个糖果 应该发挥最大的价值,所以尽可能的分给喜欢该糖果 的小孩,但该糖果可能有很多小孩喜欢,那怎么分呢,我们就可以先分给那些得到的  价值总和  与B[i]相差最大,这样才能充分利用该糖果,那么就有优先级之分了,可以用最大费用最大流来做。对于不喜欢的糖果对于每个小孩价值影响都只有1,所以可以放最后单独处理。
建图:边< u , v , cap , cost >,i:第i个糖果,j:第j个小孩
1. <vs ,  i , 1 , 0  > 
2. < i , j+N , 1 , 0 > :条件like[ j ] [ i ]==1
3. < j+N , vt , B[j] / K , K >
4. < j+N , vt , 1 , B[j]%K >
最大费用最大流跑一次后,统计一下流过与汇点vt相连的点的流量(即每个小孩分得喜欢的糖果 数),再之后就是判断。
/*
最小费用最大流:求最大费用只需费用cost取反,结果取反即可。
算法思想:先用spfa找一条最小费用的可增广流路径,再更新残流网络,数组模拟队列快
存图:邻接表
*/
#include<stdio.h>
#include<string.h>
#include<queue>
using namespace std;
const int MAXN = 10010;
const int MAXM = 100100;
const int INF = 1<<30;
struct EDG{
    int to,next,cap,flow;
    int cost;  //单价
}edg[MAXM];
int head[MAXN],eid;
int pre[MAXN], cost[MAXN] ; //点0~(n-1)

void init(){
    eid=0;
    memset(head,-1,sizeof(head));
}
void addEdg(int u,int v,int cap,int cst){
    edg[eid].to=v; edg[eid].next=head[u]; edg[eid].cost = cst;
    edg[eid].cap=cap; edg[eid].flow=0; head[u]=eid++;

    edg[eid].to=u; edg[eid].next=head[v]; edg[eid].cost = -cst;
    edg[eid].cap=0; edg[eid].flow=0; head[v]=eid++;
}

bool inq[MAXN];
int q[MAXN];
bool spfa(int sNode,int eNode,int n){
    int l=0 , r=0;

    for(int i=0; i<n; i++){
        inq[i]=false; cost[i]= -1;
    }
    cost[sNode]=0; inq[sNode]=1; pre[sNode]=-1;
    q[r++]=sNode;
    while(l!=r){
        int u=q[l++];
        if(l==MAXN)l=0;
        inq[u]=0;
        for(int i=head[u]; i!=-1; i=edg[i].next){
            int v=edg[i].to;
            if(edg[i].cap-edg[i].flow>0 && cost[v]<cost[u]+edg[i].cost){ //在满足可增流的情况下,最小花费
                cost[v] = cost[u]+edg[i].cost;
                pre[v]=i;   //记录路径上的边
                if(!inq[v]){
                    if(r==MAXN)r=0;
                    q[r++]=v;
                    inq[v]=1;
                }
            }
        }
    }
    return cost[eNode] != -1;    //判断有没有增广路
}
//反回的是最大流,最小花费为minCost
int minCost_maxFlow(int sNode,int eNode ,int& minCost,int n){
    int ans=0;
    while(spfa(sNode,eNode,n)){
        int mint=INF;
        for(int i=pre[eNode]; i!=-1; i=pre[edg[i^1].to]){
            if(mint>edg[i].cap-edg[i].flow)
                mint=edg[i].cap-edg[i].flow;
        }
        ans+=mint;
        for(int i=pre[eNode]; i!=-1; i=pre[edg[i^1].to]){
            edg[i].flow+=mint; edg[i^1].flow-=mint;
            minCost+=mint*edg[i].cost;
        }
    }
    return ans;
}

int main()
{
    int T,n,m,k , vs, vt  ;
    scanf("%d",&T);
    for(int cas = 1; cas <= T; cas++)
    {
        scanf("%d%d%d",&n,&m,&k);
        vs = 0 ; vt = m+n+1;
        int ans = 0;
        init();
        for(int i = 1 ; i <= n; i++)
            addEdg( vs , i , 1 , 0);
        int b[20] , tmp ;
        for(int i = 1; i <= m; i++){
            scanf("%d",&b[i]);
            tmp = b[i]/k;
            addEdg( i+n , vt , tmp , k );
            if(b[i]%k != 0)
                addEdg( i+n , vt , 1 , b[i]%k );
            ans += b[i];
        }
        for(int i = 1; i <= m ; i++)
         for(int j = 1; j <= n ; j++){
            scanf("%d",&tmp);
            if(tmp) addEdg( j , i+n , 1  , 0 );
         }

         int tn = n , flow[30]={0};
         n -= minCost_maxFlow( vs , vt , tmp , vt+1) ;
         for(int i = head[vt] ; ~i ; i=edg[i].next)
         {
             int u = edg[i].to - tn ;
             flow[u] += edg[i^1].flow ;
         }
         for(int i = 1 ; i <= m ; i++)
         {
              tmp = flow[i] ;
             if(tmp*k>=b[i])
                ans -= b[i] ;
             else{
                b[i] -= tmp*k ;
                ans -= tmp*k ;
                if(b[i]>n) break;
                ans -= b[i] ;
                n -= b[i] ;
             }
         }

         printf("Case #%d: ",cas);
         if(ans>0)
            printf("NO\n");
         else
            printf("YES\n");
    }
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值