pku1904求所有的完备边

        题意:有N男N女,告诉其中一个完备匹配,求其它所有的完备匹配

        分析:对于一个男生,如果还有别的可能,那么肯定可以找到一个环。但这图怎么构造就悲剧了,一直建了个错图,所以一直WA。后面还是看了别人的解题报告才。。。下面分别给出WA和AC的思想与代码

//WA
//男生喜欢女生的建双向边,然后算边连通分量,如果已匹配的边的两顶点不在同一边连通分量中
//那么只有这组关系,然后不断删边,确定关系,删边。。。
/*
下面的样例相当于两个环只有一个公共顶点相连
4
3 1 2 3
3 1 2 3
2 3 4
2 3 4
1 2 3 4
*/
#include<stdio.h>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn=2200;
vector<int>e[maxn],e1[maxn];
bool flag[maxn],inq[maxn];
int n,top,num,tnum,mat[maxn],q[maxn],DFN[maxn],low[maxn],cnt[maxn];

void Tarjan(int t,int pt)
{
	DFN[t]=low[t]=++tnum;
	inq[t]=true;
	q[top++]=t;
	
	int i,j;
	for(i=0;i<e[t].size();i++)
	{
		j=e[t][i];
		if(j==pt) continue;
		if(!DFN[j])
		{
			Tarjan(j,t);
			if(low[t]>low[j])
				low[t]=low[j];
		}
		else if(inq[j]&&low[t]>DFN[j])
			low[t]=DFN[j];
	}
	if(low[t]==DFN[t])
	{
		++num;
		do
		{
			inq[q[--top]]=false;
			cnt[q[top]]=num;
		}while(q[top]!=t);
	}
}
int main()
{
	int i,j,k,ii,jj,kk,head,tail;
	while(scanf("%d",&n)!=EOF)
	{
		for(i=1;i<=2*n;i++)
		{
			e1[i].clear();
			e[i].clear();
		}
		for(i=1;i<=n;i++)
		{
			scanf("%d",&k);
			for(j=0;j<k;j++)
			{
				scanf("%d",&ii);
				e[i].push_back(ii+n);
				e[ii+n].push_back(i);
			}
		}
		for(i=1;i<=n;i++)
		{
			scanf("%d",&j);
			e1[i].push_back(j+n);
			mat[i]=j+n;
			mat[j+n]=i;
		}
		memset(DFN,0,sizeof(DFN));
		memset(low,0,sizeof(low));
		memset(cnt,0,sizeof(cnt));
		memset(inq,false,sizeof(inq));
		for(top=0,tnum=0,num=0,i=1;i<=2*n;i++)
			if(!DFN[i])
				Tarjan(i,-1);

		memset(flag,false,sizeof(flag));
		for(head=tail=0,i=1;i<=n;i++)
			if(!flag[i]&&!flag[mat[i]]&&cnt[i]!=cnt[mat[i]])
			{
				q[tail++]=i;
				q[tail++]=mat[i];
			}
		while(head!=tail)
		{
			i=q[head++];
			flag[i]=true;
			if(e[i].size()==0) continue;
			j=e[i][0];
			for(k=0;k<e[j].size();k++)
			{
				jj=e[j][k];
				
				for(ii=0;ii<e[jj].size();ii++)
					if(e[jj][ii]==j)
					{
						swap(e[jj][e[jj].size()-1],e[jj][ii]);
						e[jj].pop_back();
						break;
					}
				if(e[jj].size()<=1)
					q[tail++]=jj;
			}
		}

		for(i=1;i<=n;i++)
		{
			if(flag[i]) continue;
			for(k=0;k<e[i].size();k++)
				if(!flag[e[i][k]]&&e[i][k]!=e1[i][0]&&cnt[i]==cnt[e[i][k]])
					e1[i].push_back(e[i][k]);
		}
		for(i=1;i<=n;i++)
		{
			sort(e1[i].begin(),e1[i].end());
			printf("%d",e1[i].size());
			for(j=0;j<e1[i].size();j++)
				printf(" %d",e1[i][j]-n);
			printf("\n");
		}
	}
	return 0;
}
AC的代码如下:
//男生喜欢女生的建单向边,对于已经搭配好的,连女的至男的。
//然后求强连通分量,对于每个男生,如果和所连接的女生在同一个强连通分量中
//那么就可以跟其在一起
#include<stdio.h>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn=4200;
vector<int>e[maxn],e1[maxn];
bool flag[maxn],inq[maxn];
int n,top,num,tnum,mat[maxn],q[maxn],DFN[maxn],low[maxn],cnt[maxn];

void Tarjan(int t,int pt)
{
	DFN[t]=low[t]=++tnum;
	inq[t]=true;
	q[top++]=t;
	
	int i,j;
	for(i=0;i<e[t].size();i++)
	{
		j=e[t][i];
		if(!DFN[j])
		{
			Tarjan(j,t);
			if(low[t]>low[j])
				low[t]=low[j];
		}
		else if(inq[j]&&low[t]>DFN[j])
			low[t]=DFN[j];
	}
	if(low[t]==DFN[t])
	{
		++num;
		do
		{
			inq[q[--top]]=false;
			cnt[q[top]]=num;
		}while(q[top]!=t);
	}
}
int main()
{
	int i,j,k,ii;
	while(scanf("%d",&n)!=EOF)
	{
		for(i=1;i<=2*n;i++)
		{
			e1[i].clear();
			e[i].clear();
		}
		for(i=1;i<=n;i++)
		{
			scanf("%d",&k);
			for(j=0;j<k;j++)
			{
				scanf("%d",&ii);
				e[i].push_back(ii+n);
			}
		}
		for(i=1;i<=n;i++)
		{
			scanf("%d",&j);
			e[j+n].push_back(i);
		}
		memset(DFN,0,sizeof(DFN));
		memset(low,0,sizeof(low));
		memset(cnt,0,sizeof(cnt));
		memset(inq,false,sizeof(inq));
		for(top=0,tnum=0,num=0,i=1;i<=2*n;i++)
			if(!DFN[i])
				Tarjan(i,-1);

		for(i=1;i<=n;i++)
		{
			for(k=0;k<e[i].size();k++)
				if(cnt[i]==cnt[e[i][k]])
					e1[i].push_back(e[i][k]);
		}
		for(i=1;i<=n;i++)
		{
			sort(e1[i].begin(),e1[i].end());
			printf("%d",e1[i].size());
			for(j=0;j<e1[i].size();j++)
				printf(" %d",e1[i][j]-n);
			printf("\n");
		}
	}
	return 0;
}



 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值