Collector'sProblem(考虑建边的问题!最大流)

本文介绍了一道关于贴纸收集的问题,通过构建最大流模型解决Bob如何通过与朋友交换贴纸获得最多不同种类贴纸的问题。文章详细解释了建边规则及最大流算法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这道题还是在考验个人对最大流的理解。最大流的考法目前来看包括最优,最大,重点就是会建边,会自己建模型。下面这道题就是运用了这种思路

这道题的重点在于建边,每一条都不能拉下。

Collector'sProblem

Input:standard input
Output: standard output
Time Limit: 5 seconds


Some candymanufacturers put stickers into candy bar packages. Bob and his friendsare collecting these stickers. They all want as many different stickersas possible, but when they buy a candy bar, they don't know whichsticker is inside.
It happens that one person has duplicates of a certain sticker.Everyone trades duplicates for stickers he doesn't possess. Since allstickers have the same value, the exchange ratio is always 1:1.
But Bob is clever: he has realized that in some cases it is good forhim to trade one of his duplicate stickers for a sticker he alreadypossesses.
Now assume, Bob's friends will only exchange stickers with Bob, andthey will give away only duplicate stickers in exchange with differentstickers they don't possess.
Can you help Bob and tell him the maximum number of different stickershe can get by trading stickers with his friends?

 

Input

 

The firstline of input contains the number of cases T (T<=20).
The first line of each case contains two integers n and m(2<=n<=10, 5<=m<=25). n is the number of people involved(including Bob), and m is the number of different stickers available.
The next n lines describe each person's stickers; the first of theselines describes Bob's stickers.
The i-th of these lines starts with a number ki<=50 indicating howmany stickers person i has.
Then follows ki numbers between 1 and m indicating which stickersperson i possesses.


Output

 

For each case, print the test casenumber together with the maximum number of different stickers Bob canget.

 

Sample Input

2
2 5
6 1 1 1 1 1 1
3 1 2 2
3 5
4 1 2 1 1
3 2 2 2
5 1 3 4 4 3

SampleOutput

Case #1: 1
Case #2: 3

 

Explanationof the sample output:
In the first case, no exchange is possible, therefore Bob can have onlythe sticker with number 1.
In the second case, Bob can exchange a sticker with number 1 against asticker with number 2 of the second person,
and then this sticker against a sticker with number 3 or 4 of the thirdperson, and now he has stickers 1, 2 and 3 or 1, 2 and 4.

题目大意:

Bob和他的朋友从糖果包装里收集贴纸。这些朋友每人手里都有一些贴纸(可能重复),并且只跟别人交换他所没有的贴纸。贴纸总是一对一交换。

Bob比这些朋友聪明,因为他意识到只跟别人交换自己没有的贴纸并不总是最优的。

假设Bob的朋友只和Bob交换,并且这些朋友只会出让手里重复贴纸来交换他们没有的不同贴纸。问Bob最终能得到的不同贴纸的最大数量。



题目分析:设有n个人,m种贴纸。对Bob的朋友(2 ~ n),

我们让点1~m表示贴纸。

让点m+i表示第i个人。

num[i][j]表示第i个人拥有j种类贴纸的个数。

因为建边建的都是流量,所以建的时候就是建呗,每种情况要考虑。

1.朋友 i 如果第 j 种贴纸的数量超过 1 则建边(i + m,j,num[ i ][ j ] - 1),表示第 i 个朋友可以交换出至多第 j 种贴纸num[ i ][ j ] - 1张(自己还要留一张)。

2.如果朋友 i 没有第 j 张贴纸,则建边(j, i + m, 1),表示可以和朋友 i 交换一张第 j 种贴纸(容量为 1 是因为只交换自己没有的,在交换了第一张以后,朋友 i 已经拥有该种贴纸,所以不会再要这种贴纸了)。

3.接下来,建立超级源汇,对所有Bob有的贴纸,建边(s,i,num[ 1 ][ i ])表示Bob有第 i 种贴纸num[ 1 ][ i ]张。

4.对所有贴纸,建边(i,t,1)(每种贴纸一张就够,因为只计算种类)。接下来只要跑一遍最大流,则流量即最终结果。

代码:

#include <stdio.h>  
#include <string.h>  
#define clear(A, X) memset(A, X, sizeof A)  
#define copy(A, B) memcpy(A, B, sizeof A)  
const int maxE = 1000000;  
const int maxN = 40;  
const int maxQ = 1000000;  
const int oo = 0x3f3f3f3f;  
struct Edge{  
    int v, n, c;  
};  
Edge edge[maxE];  
int adj[maxN], cntE;  
int Q[maxQ], head, tail;  
int d[maxN], cur[maxN], pre[maxN], num[maxN];  
int s, t, nv, n, m;  
int a[maxN][maxN];  
void addedge(int u, int v, int c){  
    edge[cntE].v = v; edge[cntE].c = c; edge[cntE].n = adj[u]; adj[u] = cntE++;  
    edge[cntE].v = u; edge[cntE].c = 0; edge[cntE].n = adj[v]; adj[v] = cntE++;  
}  
void rev_bfs(){  
    clear(num, 0);  
    clear(d, -1);  
    d[t] = 0;  
    num[0] = 1;  
    head = tail = 0;  
    Q[tail++] = t;  
    while(head != tail){  
        int u = Q[head++];  
        for(int i = adj[u]; ~i; i = edge[i].n){  
            int v = edge[i].v;  
            if(~d[v]) continue;  
            d[v] = d[u] + 1;  
            Q[tail++] = v;  
            num[d[v]]++;  
        }  
    }  
}  
int ISAP(){  
    copy(cur, adj);  
    rev_bfs();  
    int flow = 0, u = pre[s] = s, i;  
    while(d[s] < nv){  
        if(u == t){  
            int f = oo, neck;  
            for(i = s; i != t; i = edge[cur[i]].v){  
                if(f > edge[cur[i]].c){  
                    f = edge[cur[i]].c;  
                    neck = i;  
                }  
            }  
            for(i = s; i != t; i = edge[cur[i]].v){  
                edge[cur[i]].c -= f;  
                edge[cur[i] ^ 1].c += f;  
            }  
            flow += f;  
            u = neck;  
        }  
        for(i = cur[u]; ~i; i = edge[i].n) if(d[edge[i].v] + 1 == d[u] && edge[i].c) break;  
        if(~i){  
            cur[u] = i;  
            pre[edge[i].v] = u;  
            u = edge[i].v;  
        }  
        else{  
            if(0 == (--num[d[u]])) break;  
            int mind = nv;  
            for(i = adj[u]; ~i; i = edge[i].n){  
                if(edge[i].c && mind > d[edge[i].v]){  
                    cur[u] = i;  
                    mind = d[edge[i].v];  
                }  
            }  
            d[u] = mind + 1;  
            num[d[u]]++;  
            u = pre[u];  
        }  
    }  
    return flow;  
}  
void init(){  
    clear(adj, -1);  
    cntE = 0;  
}  
void work(){  
    int h, p;  
    init();  
    clear(a, 0);  
    scanf("%d%d", &n, &m);  
    s = 0; t = n + m + 1; nv = t + 1;  
    for(int i = 1; i <= n; ++i){  
        scanf("%d", &h);  
        for(int j = 0; j < h; ++j){  
            scanf("%d", &p);  
            ++a[i][p];  
        }  
    }  
    for(int i = 1; i <= m; ++i){  
        if(a[1][i]) 
addedge(s, i, a[1][i]);  
        addedge(i, t, 1);  
    }  
    for(int i = 2; i <= n; ++i) for(int j = 1; j <= m; ++j){  
        if(a[i][j] - 1 > 0) addedge(i + m, j, a[i][j] - 1);  
        else if(!a[i][j]) addedge(j, i + m, 1);  
    }  
    printf("%d\n", ISAP());  
}  
int main(){  
    int T, cas = 1;  
    for(scanf("%d", &T), cas = 1; cas <= T; ++cas){  
        printf("Case #%d: ", cas);        
        work();  
    }  
    return 0;  
}  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值