// The Problem With the Problem Setter (命題者的難題)// PC/UVa IDs: 111008/10092, Popularity: C, Success rate: average Level: 3// Verdict: Accepted// Submission Date: 2011-10-08// UVa Run Time: 0.060s//// 著作權(C)2011,邱秋。metaphysis # yeah dot net//// [Problem Description]// So many students are interested in participating in this year’s regional// programming contest that we have decided to arrange a screening test to// identify the most promising candidates. This test may include as many as 100// problems drawn from as many as 20 categories. I have been assigned the job of// setting problems for this test.//// At first the job seemed to be very easy, since I was told that I would be// given a pool of about 1,000 problems divided into appropriate categories.// After getting the problems, however, I discovered that the original authors// often wrote down multiple category-names in the category fields. Since no// problem can used in more than one category and the number of problems needed// for each category is fixed, assigning problems for the test is not so easy.//// [Input]// The input file may contain multiple test cases, each of which begins with a// line containing two integers, nk and np , where nk is the number of categories// and np is the number of problems in the pool. There will be between 2 and 20// categories and at most 1,000 problems in the pool.//// The second line contains nk positive integers, where the ith integer specifies// the number of problems to be included in category i (1 ≤ i ≤ nk ) of the test.// You may assume that the sum of these nk integers will never exceed 100. The// jth (1 ≤ j ≤ np ) of the next np lines contains the category information of// the jth problem in the pool. Each such problem category specification starts// with a positive integer specifying the number of categories in which this// problem can be included, followed by the actual category numbers.//// A test case containing two zeros for nk and np terminates the input.//// [Output]// For each test case, print a line reporting whether problems can be successfully// selected from the pool under the given restrictions, with 1 for success and// 0 for failure.//// In case of successful selection, print nk additional lines where the ith line// contains the problem numbers that can be included in category i. Problem// numbers are positive integers not greater then np and each two problem numbers// must be separated by a single space. Any successful selection will be accepted.//// [Sample Input]// 3 15// 3 3 4// 2 1 2// 1 3// 1 3// 1 3// 1 3// 3 1 2 3// 2 2 3// 2 1 3// 1 2// 1 2// 2 1 2// 2 1 3// 2 1 2// 1 1// 3 1 2 3// 3 15// 7 3 4// 2 1 2// 1 1// 1 2// 1 2// 1 3// 3 1 2 3// 2 2 3// 2 2 3// 1 2// 1 2// 2 2 3// 2 2 3// 2 1 2// 1 1// 3 1 2 3// 0 0//// [Sample Output]// 1// 8 11 12// 1 6 7// 2 3 4 5// 0//// [解題方法]// 此題和 UVa 100249 類似,可以建模成求最大流問題。設立源點 source,從源點到每道題目之間弧的// 容量為 1,每道題目到各個類別之間弧的容量為 1,題目類別到匯點 sink 之間弧的容量為該類別所要// 求的題目數,然後求此圖的最大流能否達到題目要求數。#include <iostream>#include <cstring>#include <queue>using namespace std;#define MAXCATEGORY 21#define MAXPROBLEM 1001#define MAXV 1030// 最大頂點數。#define UNSOLVABLE 0// 無安排方案。#define SOLVABLE 1// 存在安排方案。#define DUMMY (-1)// 表示頂點無父親頂點。struct edge{int vertex;// 相連的頂點。int capacity;// 容量。int flow;// 流量。int residual;// 殘餘流量。};edge edges[MAXV][MAXV];// 有向圖的邊。int degree[MAXV];// 有向圖中頂點的度。int parents[MAXV];// 遍曆標記,當前頂點的父親頂點。bool discovered[MAXV];// 遍曆標記,是否已發現。// 使用寬度優先遍曆找到一條從源點到匯點的剩餘流量為正的通路。從源到匯的任意增廣路都能增加總流量,因// 此可以借用寬度優先遍曆,需要注意的是,只能沿著“還能增廣”(即殘餘容量為正數)的邊走,因此需要在// 遍曆過程中判斷殘餘容量是否為正,以協助寬度優先遍曆區分開飽和邊和非飽和邊。void breadthFirstSearch(int source, int sink){queue < int > vertices;vertices.push(source);discovered[source] = true;while (!vertices.empty()){int v = vertices.front();vertices.pop();for (int i = 0; i < degree[v]; i++)// 檢查是否為飽和邊。if (edges[v][i].residual > 0){if (discovered[edges[v][i].vertex] == false){vertices.push(edges[v][i].vertex);discovered[edges[v][i].vertex] = true;parents[edges[v][i].vertex] = v;}if (edges[v][i].vertex == sink)return;}}}// 找到頂點 x 與頂點 y 之間的有向邊。edge *findEdge(int x, int y){for (int i = 0; i < degree[x]; i++)if (edges[x][i].vertex == y)return &edges[x][i];}// 增廣,注意對前向弧和反向弧的處理。void augmentPath(int source, int sink, int volume){if (source == sink)return;edge *e = findEdge(parents[sink], sink);e->flow += volume;e->residual -= volume;e = findEdge(sink, parents[sink]);e->residual += volume;augmentPath(source, parents[sink], volume);}// 根據 BFS 的結果,從匯點 sink 到源點 source 計算通路的容量。增廣的過程把盡量多的殘餘流量轉// 化為正流量。增廣路的容量等於整條路中殘餘容量的最小值,正如車流的速度取決於最擁擠的路段。int pathVolume(int source, int sink){if (parents[sink] == DUMMY)return 0;edge *e = findEdge(parents[sink], sink);if (source == parents[sink])return (e->residual);elsereturn (min(pathVolume(source, parents[sink]), e->residual));}// 初始化搜尋變數。void initializeSearch(){memset(discovered, false, sizeof(discovered));memset(parents, DUMMY, sizeof(parents));}// 網路流解題。每次從源到匯尋找一條可以增加總流量的路徑,並且用它增廣。當沒有增廣路存在時,演算法終// 止,此時的流就是最大流。注意需要將每條有向邊 e = (i,j) 拆分成兩條弧 (i,j) 和 (j,i),// 其中 (i,j) 的初始殘餘容量為 e 的容量,(j,i) 的殘餘容量為 0,所有的弧的初始流均設為 0。// 事實上,任意可行的流都可以作為演算法的初始流,快速構造接近最大流的可行流能大大提高演算法效率。bool netflow(int source, int sink, int nTotal){int maxFlow = 0, volume;initializeSearch();breadthFirstSearch(source, sink);volume = pathVolume(source, sink);while (volume){maxFlow += volume;augmentPath(source, sink, volume);initializeSearch();breadthFirstSearch(source, sink);volume = pathVolume(source, sink);}return maxFlow == nTotal;}int main(int ac, char *av[]){int nCategories, nProblems;int nTotal, nCount, nType;int category[MAXCATEGORY];// 每種類別的題目數量要求。int problem[MAXCATEGORY];// 每種類型的現有題目數量。int source, sink;bool solvable;while (cin >> nCategories >> nProblems, nCategories || nProblems){source = nTotal = 0;sink = nCategories + nProblems + 1;memset(degree, 0, sizeof(degree));memset(problem, 0, sizeof(problem));for (int i = 1; i <= nCategories; i++){cin >> category[i];nTotal += category[i];// 源點到類別。edges[source][degree[source]++] = (edge){i, category[i], 0, category[i]};edges[i][degree[i]++] = (edge){source, category[i], 0, 0};}for (int i = nCategories + 1; i <= (nCategories + nProblems); i++){cin >> nCount;for (int j = 1; j <= nCount; j++){cin >> nType;problem[nType]++;// 類別到題目。edges[nType][degree[nType]++] = (edge){i, 1, 0, 1};edges[i][degree[i]++] = (edge){nType, 1, 0, 0};}// 題目到匯點。edges[i][degree[i]++] = (edge){sink, 1, 0, 1};edges[sink][degree[sink]++] = (edge){i, 1, 0, 0};}if (nCategories == 0){cout << SOLVABLE << "\n";continue;}if (nProblems == 0){cout << UNSOLVABLE << "\n";continue;}// 若某類型的題目數量少於要求的總數,則肯定不可解。solvable = true;for (int i = 1; i <= nCategories; i++)if (problem[i] < category[i]){solvable = false;break;}if (solvable == false){cout << UNSOLVABLE << "\n";continue;}solvable = netflow(source, sink, nTotal);cout << (solvable ? SOLVABLE : UNSOLVABLE) << "\n";if (!solvable)continue;for (int i = 1; i <= nCategories; i++){int blank = 0;for (int j = 0; j < degree[i]; j++){if (edges[i][j].residual == 0){cout << (blank++ ? " " : "");cout << (edges[i][j].vertex - nCategories);}}cout << "\n";}}return 0;}