POJ3281 Dining (拆点+最大流)

题意:

N头牛,准备了F种食物,D种饮料,每一头牛会喜欢若干种食物和饮料,但它只能选择一种食物和一种饮料,且每种食物和饮料都只够一头牛选择,问怎样分配能使得食物和饮料都能得到的牛的数量最多,求这个数。

思路:

明显的一道最大流的题目,难点只在怎么建模。建模的方法如下:

1.建立一个超级源,跟每种食物之间连一条容量为1的边;

2.建立一个超级汇,它与每种饮料之间有一条容量为1的边;

3.将每头牛都拆分成两个点C1、C2,两点之间有一条容量为1的边;

4.若一头牛喜欢食物f,就将其对应的C1点与f连接起来,容量为1,若一头牛喜欢饮料d,同理将C2与d连接起来。

为何要拆点?

不拆点不能保证牛只能选择一种食物和一种饮料这一条件。即限定牛结点的容量为1。


我的代码里1到f为食物点,f+1到f+2*n为牛点,f+2*n+1到f+2*n+d为饮料点

#include<cstdio>
#include<cstring>
using  namespace std;

const int maxn=500;//点数的最大值
const int maxm=250000;//边数的最大值
const int inf=0x3f3f3f3f;
struct Edge {
  int to,next,cap,flow;
}edge[maxm];//注意是maxm
int tol;
int head[maxn];
int cur[maxn],d[maxn];// 当前弧下标   结点到汇点距离下界
int p[maxn],gap[maxn];//可增广路上的上一条弧   gap优化  //比dinic多的两个数组

void init() {
  tol=0;
  memset(head,-1,sizeof(head));
}

//加边,单向图三个参数,双向图四个参数
void addedge(int u,int v,int w,int rw=0) {
  edge[tol].to=v; edge[tol].cap=w; edge[tol].next=head[u];
  edge[tol].flow=0; head[u]=tol++;
  edge[tol].to=u; edge[tol].cap=rw; edge[tol].next=head[v];
  edge[tol].flow=0; head[v]=tol++;
}

//输入参数:起点、终点、点的总数
//点的编号没有影响,只要输入点的总数
int sap(int s,int t,int N){
  memset(gap, 0, sizeof(gap));
  memset(d, 0, sizeof(d));
  memcpy(cur, head, sizeof(head));
  int u=s;
  p[u]=-1;
  gap[0]=N;
  int ans=0;
  while(d[s]<N){
    if(u == t){
      int Min=inf;
      for(int i=p[u]; i!=-1; i=p[edge[i^1].to])//找最小残量值
        if(Min>edge[i].cap-edge[i].flow)
          Min=edge[i].cap-edge[i].flow;
      for(int i = p[u]; i!=-1; i=p[edge[i^1].to]){//增广
        edge[i].flow+=Min;
        edge[i^1].flow-=Min;
      }
      u=s;
      ans+=Min;
      continue;
    }
    bool ok=false;
    int v;
    for(int i=cur[u]; i!=-1; i=edge[i].next){
        v=edge[i].to;
        if(edge[i].cap-edge[i].flow && d[v]+1==d[u]){//Advance前进
          ok=true;
          cur[u]=p[v]=i;
          break;
        }
    }
    if(ok){
      u=v;
      continue;
    }
    //Retreat走不动了,撤退
    int Min=N;
    for(int i=head[u]; i!=-1; i=edge[i].next)
      if(edge[i].cap-edge[i].flow && d[edge[i].to] < Min){
        Min=d[edge[i].to];
        cur[u]=i;
      }
    gap[d[u]]--;
    if(!gap[d[u]])return ans;
    d[u] = Min+1;
    gap[d[u]]++;
    if(u!=s) u=edge[p[u]^1].to;//退一步,沿父边返回
  }
  return ans;
}


int main(){
  #ifndef ONLINE_JUDGE
  freopen("in.txt","r",stdin);
  #endif

  int f,d,n;
  int fnum,dnum,tmp,source,sink;
  while(~scanf("%d%d%d",&n,&f,&d)){
    init();
    source=0;sink=2*n+f+d+1;
    for(int i=1; i<=f; i++)addedge(source, i, 1);//源点指向食物
    for(int i=1; i<=d; i++)addedge(f+2*n+i, sink, 1);//饮料指向汇点
    for(int i=1; i<=n; i++)addedge(f+i, f+n+i, 1);//牛指向牛

    for(int i=1; i<=n ;i++){
      scanf("%d%d",&fnum,&dnum);
      while(fnum--){
        scanf("%d",&tmp);
        addedge(tmp, f+i, 1);//食物指向牛
      }
      while(dnum--){
        scanf("%d",&tmp);
        addedge(f+n+i, f+2*n+tmp, 1); //牛指向饮料
      }
    }
    printf("%d\n",sap(source, sink, f+2*n+d+2));
  }
  return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值