ZOJ2470 POJ1904 King's Quest,强连通分量

蛮好的一道题,初看觉得是二分图匹配,但是仔细思索却是强连通分量。关于题目解释可以看这里点击打开链接

题意:有n个王子,有n个美女,每个王子可能同时喜欢多个美女,数据已经给出一组完全匹配的方案。问在满足所有王子都能完全匹配的情况下,每个王子能选择的对象分别有谁,按升序输出。(其实如标题的描述更简洁。。)

  一开始可能会想,对于每个王子,先拆边,再重新挑选对象,判断可行性。注意到题目数据量很大,最多2000个节点,200000条边,如果直接暴力拆边判可行,复杂度就是O(N*N*M)……注意到题目已经给出了一组合法的方案,肯定是有用的。  假设 A 王子和原配 B 美女解除关系,再匹配了 C 美女,那么 C 美女的原配 D 王子必定要重新再找另一个美女,如果任意一个王子能找回 B 美女匹配,证明 A 王子和 C 美女是可能的匹配对,否则就是不可能的匹配对。

  构图:每个王子向喜欢的美女连接一条有向边,再根据匹配好的方案,每个美女向其匹配的王子连接一条有向边。。。构图后简化一下描述就是:如果从 A 点出发,最终能回到 A 点(成环)则能够维持完全匹配的,因此求一次强连通分量,判断每个王子与其喜欢的美女是否在同一强连通分量即可。

这里再补充一点,对于这个图中的每一个强连通分量,里面的男和女的数目都是一样的。这是因为男的只连去女的,女的只连去男的,且女的连去男的只有一根连线。

/*******************************************************************************
 # Author : Neo Fung
 # Email : [email protected]
 # Last modified: 2012-07-24 19:23
 # Filename: ZOJ2470 POJ1904 King's Quest.cpp
 # Description : 
 ******************************************************************************/
#ifdef _MSC_VER
#define DEBUG
#define _CRT_SECURE_NO_DEPRECATE
#endif

#include <fstream>
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <string>
#include <limits.h>
#include <algorithm>
#include <math.h>
#include <numeric>
#include <functional>
#include <ctype.h>
#include <vector>
using namespace std;

const int kMAX=10010;
const double kEPS=10E-6;
int STACK[kMAX],top=0;          //Tarjan 算法中的栈 
bool InStack[kMAX];             //检查是否在栈中 
int DFN[kMAX];                  //深度优先搜索访问次序 
int Low[kMAX];                  //能追溯到的在栈中的最早次序
int ComponentNumber=0;       //有向图强连通分量个数 
int Index=0;                 //索引号 
vector <int> Edge[kMAX];        //邻接表表示 
int InComponent[kMAX];			//记录每个点在第几号强连通分量里
int ComponentDegree[kMAX];    	 //记录每个强连通分量的度
void Tarjan(int i) 
{ 
	int j; 
	DFN[i]=Low[i]=Index++; 
	InStack[i]=true; 
	STACK[++top]=i; 
	for (size_t e=0;e<Edge[i].size();e++) 
	{ 
		j=Edge[i][e]; 
		if (DFN[j]==-1) 
		{ 
			Tarjan(j); 
			Low[i]=min(Low[i],Low[j]); 
		} 
		else if (InStack[j]) //如果指向的节点j仍在栈中,由于j先于i入栈,则j有到i的通路,同时由于i指向j,则i与j构成回路
			Low[i]=min(Low[i],DFN[j]); 	//如果指向的节点扔在栈中,则指向的节点仍未编入强连通分量
		//如果前面两个判断条件都是错误的话,则i和j不在同一个连通分量中
	} 
	if (DFN[i]==Low[i]) //连通分量中最早进栈的点
	{ 
		ComponentNumber++; 
		do 
		{ 
			j=STACK[top--]; 
			InStack[j]=false; 
			InComponent[j]=ComponentNumber;	//给每一个连通分量上的节点染色
		} 
		while (j!=i); 
	} 
}

int output[kMAX];
void solve(int N)     //N是此图中点的个数,注意是0-indexed! 
{ 
	memset(STACK,-1,sizeof(STACK)); 
	memset(InStack,0,sizeof(InStack)); 
	memset(DFN,-1,sizeof(DFN)); 
	memset(Low,-1,sizeof(Low)); 

	for(int i=1;i<=N;i++) 
		if(DFN[i]==-1) 
			Tarjan(i);

	int n=N/2;
	for(int i=1;i<=n;++i)
	{
		int x=InComponent[i];
		size_t cnt=0;
		for(size_t j=0;j<Edge[i].size();++j)
			if(InComponent[Edge[i][j]]==x)
				output[cnt++]=Edge[i][j]-n;

		sort(output,output+cnt);
		printf("%d",cnt);
		for(size_t j=0;j<cnt;++j)
			printf(" %d",output[j]);
		printf("\n");

	}
// 	printf("\n");
}

int main(void)
{
#ifdef DEBUG  
  freopen("../stdin.txt","r",stdin);
  freopen("../stdout.txt","w",stdout); 
#endif  

  int n,tmp,u,v;

  while(~scanf("%d",&n) && n)
  {
	  for(int i=0;i<=2*n;++i)
		  Edge[i].clear();
	  for(int i=1;i<=n;++i)
	  {
		  scanf("%d",&tmp);
		  while(tmp--)
		  {
			  scanf("%d",&v);
			  Edge[i].push_back(v+n);
		  }
	  }
	  for(int i=1;i<=n;++i)
	  {
		  scanf("%d",&u);
		  Edge[u+n].push_back(i);
	  }
	  solve(n+n);
  }

  return 0;
}


你可能感兴趣的:(c,算法,vector,email,output)