poj 1417 True Liars 解题报告 并查集 DP

思路并不复杂, 并查集的应用 然后再用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

这个例子也说明有时候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;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值