题意
Bob 和朋友换贴纸,他和朋友们都有若干贴纸,Bob 希望通过交换来使自己持有更多种类的贴纸,给出每个人的贴纸持有信息,求Bob 经过交换后能持有的最大贴纸种类数。
Input
格式:
先给出样例数量,每个样例第一行给出参与交换的人数n(包括Bob)和参与交换的贴纸种类数m,接着n行,每行描述一个人,第一行为Bob。先给出此人拥有的贴纸数k,而后k个数代表贴纸的种类
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
Output
Case #1: 1
Case #2: 3
思路
很标准的网络流模板题,只要求一个数,不要求打印方案,稍微有点绕,就是起点和终点都是Bob,可以分成两个Bob。
网络流最重要是建好图(以下边均为单向边),
首先连接 Bob—>他拥有的贴纸建边,权重为Bob持有的该种类贴纸数;
对贴纸和朋友建边,连接 朋友所没有的贴纸—>朋友,权重为1,表示该朋友缺少1张该种类的贴纸;
而后连接 朋友—>朋友多余的贴纸,权重为所持有贴纸数量-1,表示该朋友可以拿出来交换的贴纸;
最后连接 Bob没有的贴纸—>Bob,权重为1,表示Bob 缺少1张该种类的贴纸;
建好图后,直接一个dinic或者edmonds_karp求出答案即可。
样例一图示(这里为了看的清楚没有标出残余图的边):
样例二图示(剩余图的边没有标出):
Code
代码前面为网络流两个算法的模板
#include <bits/stdc++.h>
#define inf 0xfffffff
using namespace std;
typedef vector<int> vi;
typedef pair<int, int> II;
struct EDGE{
int u, v, cap, flow;EDGE(){
}EDGE(int a, int b, int c, int d):u(a), v(b), cap(c), flow(d){
}
};vector<EDGE> EL;
vector<vi> AL;
vi d, last;
vector<II> p;
int cnt;bool BFS(int s, int t){
d.assign(cnt, -1); d[s] = 0;queue<int> q; q.push(s);p.assign(cnt, {
-1, -1});while(!q.empty() && d[t] == -1){
int u = q.front(); q.pop();for(int i = 0; i < AL[u].size(); ++i){
auto& e1 = EL[AL[u][i]];if(e1.cap > e1.flow && d[e1.v] == -1)d[e1.v] = d[e1.u] + 1, q.push(e1.v), p[e1.v] = {
e1.u, AL[u][i]};}}return d[t] != -1;
}int DFS(int s, int t, int f){
if(s == t || f == 0) return f;for(; last[s] < AL[s].size(); ++last[s]){
auto& e1 = EL[AL[s][last[s]]];if(d[e1.v] != d[e1.u] + 1) continue;int pushed = DFS(e1.v, t, min(f, e1.cap - e1.flow));if(pushed > 0){
e1.flow += pushed;auto& e2 = EL[AL[s][last[s]]^1];e2.flow -= pushed;return pushed;}}return 0;
}int send_one_flow(int s, int t, int f){
if(s == t) return f;auto& pa = p[t];auto& e1 = EL[pa.second];int pushed = send_one_flow(s, pa.first, min(f, e1.cap - e1.flow));e1.flow += pushed;auto& e2 = EL[pa.second^1];e2.flow -= pushed;return pushed;
}int edmonds_karp(int s, int t){
int mf = 0;while(BFS(s, t)){
int f = send_one_flow(s, t, inf);if(f == 0) break;mf += f;}return mf;
}int dinic(int s, int t){
int mf = 0;while(BFS(s, t)){
last.assign(cnt, 0);while(int f = DFS(s, t, inf))mf += f;}return mf;
}void init(){
EL.clear();AL.assign(cnt, vi());
}void add_edge(int u, int v, int w, int directed){
if(u == v) return ;EL.emplace_back(u, v, w, 0);AL[u].push_back(EL.size() - 1);EL.emplace_back(v, u, directed ? 0 : w, 0);AL[v].push_back(EL.size() - 1);
}int num[12][28];int main(){
int t, c = 1; cin >> t;while(t--){
int n, m; cin >> n >> m;cnt = n + m + 2;memset(num, 0, sizeof(num));for(int i = 0; i < n; ++i){
int sum; cin >> sum;for(int j = 0; j < sum; ++j){
int v; cin >> v;++num[i][v];//统计所有人的贴纸拥有数}}init();for(int i = 1; i <= m; ++i)add_edge(0, i, num[0][i], 1);/建立Bob到贴纸的单向边for(int i = 1; i < n; ++i){
for(int j = 1; j <= m; ++j){
if(num[i][j] == 0) add_edge(j, i + m, 1, 1);//建立贴纸到朋友的单向边else add_edge(m + i, j, num[i][j] - 1, 1);//建立朋友到贴纸的单向边}}for(int i = 1; i <= m; ++i){
add_edge(i, cnt - 1, 1, 0);//建立贴纸到Bob2的单向边}cout << "Case #" << c++ << ": " << dinic(0, cnt - 1) << endl;}
}