思路并不复杂, 并查集的应用 然后再用DP去找解的个数。并查集和DP并没有结合在一起,相当于做了两道题吧,呵呵。 看过网上的该题的一些代码,发现大多数都有bugs。这些代码虽然能通过poj的测试,但还是错的,其中大部分代码过不了下面这个测试数据:
11 9 7
6 12 no
4 8 yes
3 12 yes
5 9 no
6 11 yes
6 5 no
15 4 yes
16 15 yes
10 3 no
6 16 no
2 6 no
0 0 0
11 9 7
6 12 no
4 8 yes
3 12 yes
5 9 no
6 11 yes
6 5 no
15 4 yes
16 15 yes
10 3 no
6 16 no
2 6 no
0 0 0
这个例子也说明有时候poj的测试数据其实也不够强。
我的代码如下,代码注释很清晰了,没怎么优化,poj 测试通过,时间0 Ms, 空间 1596 K. 自认为是很正确的版本,希望有人也来为我纠错。
/* poj_1417.
* AC: Time 0 Ms, Space 1596 K.
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
using namespace std;
#define MAXM 2*MAXS // Max #[people].
#define MAXS 300 // Max #[people in a tribe].
int father[MAXM + 1], rank[MAXM + 1], opposite[MAXM + 1];
/* sets[i] is the set with i being its root.
* sets[i][0] is the number of elems in this set.
* sets[i][1,...] are the elems in this set.
*/
int sets[MAXM + 1][MAXM + 1];
/* A group is defined to be a pair of sets that are opposite.
* root, size are information of one set.
* oproot, opsize are information of the opposite set.
*/
struct Group
{
int root;
int size;
int oproot;
int opsize;
};
/* groups[i] store the information about a group.
*/
Group groups[MAXM + 1];
int numGroups; // Total number of groups.
bool isCounted[MAXM + 1]; // Used to collect groups.
/* The following is used by DP. */
int divines[MAXM];
int table[2][MAXM];
int trace[MAXM][MAXM];
/* Disjoint set operations. *//*{{{*/
void makeAllSets(int m)
{
for(int i = 1; i <= m; ++i)
{
father[i] = i;
rank[i] = 0;
opposite[i] = -1;
}
}
int findSet(int x)
{
if(x != father[x])
{
father[x] = findSet(father[x]);
}
return father[x];
}
/* @return root of the union. */
int unionSets(int sx, int sy)
{
if(sx == sy)
return sx;
if(rank[sx] < rank[sy])
{
father[sx] = sy;
return sy;
}
else
{
if(rank[sx] == rank[sy])
{
rank[sx]++;
}
father[sy] = sx;
return sx;
}
}/*}}}*/
void initilaize(int m)
{
/* sets[0] is a sentinel set standing for an empty set.
* All elems are >= 1. */
for(int i = 0; i <= m; ++i)
{
sets[i][0] = 0;
isCounted[i] = false;
}
}
/* Organize the disjoint forest to groups.
* First store all sets to set. Then collect groups using sets.
*/
bool collectGroups(int m)
{
/* Collect sets. */
for(int i = 1; i <= m; ++i)
{
int si = findSet(i);
sets[si][++sets[si][0]] = i;
}
/* Get groups data. */
numGroups = 0;
for(int i = 1; i <= m; ++i)
{
if(sets[i][0] > 0 && !isCounted[i])
{
/* Get information of set i. */
groups[numGroups].root = i;
groups[numGroups].size = sets[i][0];
isCounted[i] = true;
/* Get information of opposite set of i. */
int si = findSet(i);
if(opposite[si] == -1)
{ // When si has no opposite set, its opposite set size is 0.
// And we set its opposite set to 0, which is a sentinel
// standing for empty set.
groups[numGroups].oproot = 0;
groups[numGroups].opsize = 0;
}
else
{
int soi = findSet(opposite[si]);
groups[numGroups].oproot = soi;
groups[numGroups].opsize = sets[soi][0];
isCounted[soi] = true;
}
/* When two sets has the same size in a group, there cannot be
* a unique solution. So we can stop and output the result to
* save time.
*/
if(groups[numGroups].size == groups[numGroups].opsize)
{
return true;
}
numGroups++;
}
}
return false;
}
/* Let f(i, j) be #[formulations] that form j using only groups[0,...,i-1].
* Then:
* f(0, j) = \{
* 1 if j = 0,
* 0 otherwise.
* f(i, j) = f(i-1, j-groups[i-1].size) * I_{j >= groups[i-1].size} +
* f(i-1, j-groups[i-1].opsize) * I_{j >= groups[i-1].opsize}
* 1 <= i <= numGroups, 0 <= j <= numDivines.
*
* Time complexity: O(numGroups * numDivines).
* Space complexity: \Theta(numGroups * numDivines).
*/
int dpNumFormulations(int numDivines)
{
table[0][0] = 1;
for(int j = 1; j <= numDivines; ++j)
{
table[0][j] = 0;
}
int *row1, *row2;
row1 = table[0]; row2 = table[1];
for(int i = 1; i <= numGroups; ++i)
{
for(int j = 0; j <= numDivines; ++j)
{
row2[j] = 0;
/* trace[i][j] = 0 means neither sets are used.
* = 1 means only set is used.
* = 2 means only opposite set is used.
* = 3 means both sets are used.
*/
trace[i][j] = 0; // Initialize to 0.
if(j >= groups[i-1].size)
{
row2[j] += row1[j - groups[i-1].size];
if(row1[j - groups[i-1].size] > 0)
{
trace[i][j] = 1; // Set last bit to 1 if use set.
}
}
if(j >= groups[i-1].opsize)
{
row2[j] += row1[j - groups[i-1].opsize];
if(row1[j - groups[i-1].opsize] > 0)
{
trace[i][j] |= 2; // Set second bit to 1 if use opposite set.
}
}
row2[j] = min(row2[j], 2); // Cap to 2.
}
swap(row1, row2);
}
return row1[numDivines];
}
void restoreSolution(int numDivines)
{
int num = 0;
int j = numDivines;
for(int i = numGroups; i > 0; --i)
{
int s;
if(trace[i][j] == 1)
{ // Set groups[i-1].root is used
s = groups[i-1].root;
}
else
{ // Set groups[i].oproot is used. */
assert(trace[i][j] == 2);
s = groups[i-1].oproot;
}
/* Puting used set to divines. */
for(int k = 1; k <= sets[s][0]; ++k)
{
divines[num++] = sets[s][k];
}
j -= sets[s][0];
}
assert(num == numDivines);
/* Print the solution. */
sort(divines, divines + numDivines);
for(int i = 0; i < numDivines; ++i)
{
printf("%d\n", divines[i]);
}
printf("end\n");
}
int main()
{
int n, m, p1, p2, x, y;
char ans[4];
while(scanf("%d%d%d", &n, &p1, &p2) && !(n == 0 && p1 == 0 && p2 == 0))
{
m = p1 + p2; // Total #[people].
/* Build opposite groups using disjoint forest.
* O(n * \alpha(m)) time.
*
* When building sets, we maintain the following property:
* For each root of a set, opposite[root] must point to one elem in
* its opposite set, if it has an opposite set.
*/
makeAllSets(m);
for(int i = 0; i < n; ++i)
{
scanf("%d%d", &x, &y);
scanf("%s", ans);
int sx = findSet(x);
int sy = findSet(y);
if(strcmp(ans, "no") == 0)
{ // x and y are in different tribes.
if(opposite[sx] != -1 && opposite[sy] == -1)
{
int sox = findSet(opposite[sx]);
int su = unionSets(sy, sox);
opposite[su] = x; // Need to set the opposite of union.
}
else if(opposite[sx] == -1 && opposite[sy] != -1)
{
int soy = findSet(opposite[sy]);
int su = unionSets(sx, soy);
opposite[su] = y; // Need to set the opposite of union.
}
else if(opposite[sx] != -1 && opposite[sy] != -1)
{
int sox = findSet(opposite[sx]);
int soy = findSet(opposite[sy]);
unionSets(sx, soy); // Not need to set opposite of union.
unionSets(sy, sox);
}
else // opposite[sx] == -1 && opposite[sy] == -1
{
opposite[sx] = y;
opposite[sy] = x;
}
}
else
{ // x and y are in the same tribe.
int su = unionSets(sx, sy);
if(opposite[sx] == -1 && opposite[sy] != -1)
{
int soy = findSet(opposite[sy]);
opposite[su] = soy; // Need to set the opposite of union.
}
else if(opposite[sx] != -1 && opposite[sy] == -1)
{
int sox = findSet(opposite[sx]);
opposite[su] = sox; // Need to set the opposite of union.
}
else if(opposite[sx] != -1 && opposite[sy] != -1)
{
int sox = findSet(opposite[sx]);
int soy = findSet(opposite[sy]);
unionSets(sox, soy); // Not need to set opposite of union.
}
}
}
initilaize(m); // O(m) time.
/* Collect group data. O(m) time. */
bool isCut = collectGroups(m);
if(isCut)
{
printf("no\n"); // Early stop to save time.
continue;
}
/* Use DP to solve the problem. O(numGroups * p1) time. */
int numFormulations = dpNumFormulations(p1);
if(numFormulations != 1)
{
printf("no\n");
}
else
{
restoreSolution(p1);
}
}
return 0;
}