1. 程式人生 > >poj 3281

poj 3281

       這是一道最大流的題,在構圖上比上一道更難一些。

       做這道題時,用了Dinic演算法。寫了兩個程式,一個是鏈式前向星存圖(參考《ACM—ICPC程式設計系列 圖論及應用》(哈爾濱工業大學出版社)P160),一個是基於STL的鄰接表(參考《挑戰程式設計競賽》 人民郵電出版社 P216)。

       Dinic演算法與之前的EK演算法和Ford-Fulkerson演算法相比效率更高,因為其先對圖進行分層,然後多路增廣,所以效率更高。基本思路是先有寬度優先搜尋把圖分層,然後根據分層的情況進行深度優先搜尋,當無法增廣時,再次分層,如果分層無法到達匯點,則結束(詳細參考上面的兩本書)。所謂多路增廣,就是可以一次DFS就可以得到多條增廣路,這一點在鏈式前向星版本體現的明顯些。而在鄰接表版本中,則通過當前弧對DFS進行優化,多路增廣的特性似乎沒有很好體現。

        通過在poj上多次提交發現,鏈式前向星版本快於鄰接表版本,可能是因為這個鄰接表基於STL比較慢,而且每次處理完一個測試用例要clear把圖清空,也增加了時間開銷。在上面說到的細節優化採取的策略上也有所影響。

        雖然是同一個演算法,但是由於存圖的方式不同,所以細節處理不同,其中的差異值得去學習。

        與上次同樣,第一個可以用C++交(G++沒試過),第二個必須用G++交。

程式碼(C++,鏈式前向星):

#include <cstdlib>
#include <iostream>
#include <queue>
#include <algorithm>

#define MAX 409
#define INF 2000000000
using namespace std;

//#define LOCAL

struct Edge{
    int to;
    int next;
    int cap;   
} edge[MAX*MAX*MAX*2];

int head[MAX],c,level[MAX];

void add_edge(int u,int v,int cap)
{
     edge[c].to=v;
     edge[c].next=head[u];
     edge[c].cap=cap;
     head[u]=c;
     c++;
     
     edge[c].to=u;
     edge[c].next=head[v];
     edge[c].cap=0;
     head[v]=c;
     c++;
}

int bfs(int s)
{
    int u,v,i;
    queue<int> qi;    
    memset(level,-1,sizeof(level));
    qi.push(s);
    level[s]=0;
    while(!qi.empty())
    {
         u=qi.front();
         qi.pop(); 
         for(i=head[u];i!=-1;i=edge[i].next)
         {
             v=edge[i].to;
             if(edge[i].cap>0&&level[v]==-1)
             {
                 level[v]=level[u]+1;
                 qi.push(v);                           
             }                    
         }             
    }
}

int dfs(int s,int t,int f)
{
    int i,v,d,res;
    if(s==t) return f;
    res=0;
    for(i=head[s];i!=-1;i=edge[i].next)
    {
         v=edge[i].to;                              
         if(level[s]+1==level[v]&&edge[i].cap>0)
         {
              d=dfs(v,t,min(edge[i].cap,f-res)); 
              if(d>0)
              {
                  edge[i].cap-=d;
                  edge[i^1].cap+=d;
                  res+=d;  
                  if(res==f) return res; 
              }                          
         }                               
    } 
    return res;
}

int dinic(int s,int t)
{
    int f,flow; 
    flow=0;     
    while(1)
    {
         bfs(s);
         if(level[t]==-1) break;
         f=dfs(s,t,INF);
         flow+=f;    
    }
    return flow; 
}

int main(int argc, char *argv[])
{
#ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif

    int n,f,d,fi,di,p,i,j;
    while(scanf("%d %d %d",&n,&f,&d)!=EOF)//超級源點為0,超級匯點為d+300+1 
    {                                     //食物編號:1~f  牛編號:1+100~n+100,1+200~n+200  飲料編號:1+300~d+300                                 
        c=0;            
        memset(head,-1,sizeof(head));  
        
        for(i=1;i<=f;i++)  add_edge(0,i,1); //新增超級源點到食物的邊       
        for(i=1;i<=n;i++)
        {
            scanf("%d %d",&fi,&di);
            for(j=0;j<fi;j++)
            {
                scanf(" %d",&p);
                add_edge(p,i+100,1);  //新增食物到牛的邊                           
            }
            add_edge(i+100,i+200,1); 
            for(j=0;j<di;j++)
            {
                scanf(" %d",&p);                                                                                           
                add_edge(i+200,p+300,1);  //新增牛到飲料的邊                                              
            }                     
        }
        for(i=1;i<=d;i++) add_edge(i+300,d+300+1,1); //新增飲料到超級匯點的邊 

        printf("%d\n",dinic(0,d+300+1));           
    } 
    system("PAUSE");
    return EXIT_SUCCESS;
}

程式碼(G++,鄰接表):

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
#include <cstring>

#define MAX 409
#define INF 2000000000
using namespace std;

struct Edge{
    int to;
    int cap;
    int rev;
};
vector<Edge> G[MAX];

int level[MAX],iter[MAX];

void add_edge(int from,int to,int cap)
{
    G[from].push_back((Edge){to,cap,G[to].size()});
    G[to].push_back((Edge){from,0,G[from].size()-1});
}

void bfs(int s)
{
    int i,u;
    queue<int> qi;
    memset(level,-1,sizeof(level));
    qi.push(s);
    level[s]=0;
    while(!qi.empty())
    {
        u=qi.front();
        qi.pop();
        for(i=0;i<G[u].size();i++)
        {
            Edge &e=G[u][i];
            if(e.cap>0&&level[e.to]==-1)
            {
                level[e.to]=level[u]+1;
                qi.push(e.to);
            }
        }
    }
}

int dfs(int u,int t,int f)
{
    int d;
    if(u==t) return f;
    for(int &i=iter[u];i<G[u].size();i++)
    {
        Edge &e=G[u][i];
        if(e.cap>0&&level[u]==level[e.to]-1)
        {
            d=dfs(e.to,t,min(f,e.cap));
            if(d>0)
            {
                e.cap-=d;
                G[e.to][e.rev].cap+=d;
                return d;
            }
        }
    }
    return 0;
}

int dinic(int s,int t)
{
    int flow,f;
    flow=0;
    while(1)
    {
        bfs(s);
        if(level[t]==-1) break;
        memset(iter,0,sizeof(iter));
        while((f=dfs(s,t,INF))!=0) flow+=f;
    }
    return flow;
}

int main()
{

    //freopen("in.txt","r",stdin);

    int n,f,d,fi,di,p,i,j;
    while(scanf("%d %d %d",&n,&f,&d)!=EOF)  //超級源點為0,超級匯點為d+300+1
    {                                       //食物編號:1~f  牛編號:1+100~n+100,1+200~n+200  飲料編號:1+300~d+300
        for(i=1;i<=f;i++)  add_edge(0,i,1); //新增超級源點到食物的邊
        for(i=1;i<=n;i++)
        {
            scanf("%d %d",&fi,&di);
            for(j=0;j<fi;j++)
            {
                scanf(" %d",&p);
                add_edge(p,i+100,1);  //新增食物到牛的邊
            }
            add_edge(i+100,i+200,1);
            for(j=0;j<di;j++)
            {
                scanf(" %d",&p);
                add_edge(i+200,p+300,1);  //新增牛到飲料的邊
            }
        }
        for(i=1;i<=d;i++) add_edge(i+300,d+300+1,1); //新增飲料到超級匯點的邊

        printf("%d\n",dinic(0,d+300+1));

        for(i=0;i<=d+300+1;i++) G[i].clear();
    }
    return 0;
}

題目(http://poj.org/problem?id=3281): Dining
Time Limit: 2000MS Memory Limit: 65536K

Description

Cows are such finicky eaters. Each cow has a preference for certain foods and drinks, and she will consume no others.

Farmer John has cooked fabulous meals for his cows, but he forgot to check his menu against their preferences. Although he might not be able to stuff everybody, he wants to give a complete meal of both food and drink to as many cows as possible.

Farmer John has cooked F (1 ≤ F ≤ 100) types of foods and prepared D (1 ≤ D ≤ 100) types of drinks. Each of his N (1 ≤ N ≤ 100) cows has decided whether she is willing to eat a particular food or drink a particular drink. Farmer John must assign a food type and a drink type to each cow to maximize the number of cows who get both.

Each dish or drink can only be consumed by one cow (i.e., once food type 2 is assigned to a cow, no other cow can be assigned food type 2).

Input

Line 1: Three space-separated integers: NF, and D 
Lines 2..N+1: Each line i starts with a two integers Fi and Di, the number of dishes that cow i likes and the number of drinks that cow i likes. The next Fi integers denote the dishes that cow i will eat, and the Di integers following that denote the drinks that cow i will drink.

Output

Line 1: A single integer that is the maximum number of cows that can be fed both food and drink that conform to their wishes

Sample Input

4 3 3
2 2 1 2 3 1
2 2 2 3 1 2
2 2 1 3 1 2
2 1 1 3 3

Sample Output

3

Hint

One way to satisfy three cows is: 
Cow 1: no meal 
Cow 2: Food #2, Drink #2 
Cow 3: Food #1, Drink #1 
Cow 4: Food #3, Drink #3 
The pigeon-hole principle tells us we can do no better since there are only three kinds of food or drink. Other test data sets are more challenging, of course.