zoukankan      html  css  js  c++  java
  • 等效集合 图论(缩点)

    [题目描述]
    要使两个集合 A,B 是等效的,我们可以先让 A 成为 B 的子集,然后再让 B 成为 A 的子集,
    这样就完成了。
    使用上面的方法,我们要让 N 个集合都等效:每一步,你可以让集合 X 成为 Y 的子集。注
    意,有一些集合是已经是其他集合的子集了。求操作最少需要经过多少步?
    [输入数据]
    输入包含多组测试数据,每组数据的第一行是两个整数 N,M,接下来 M 行,每行两个数 X,
    Y,表示集合 X 已经是 Y 集合的子集。
    [输出数据]
    对于每组测试数据,输出一行,一个数,表示最少要经过的步数
    [输入样例]
    4 0
    3 2
    1 2
    1 3
    [输出样例]
    4
    2
    [数据范围]
    对于 50%的数据, N <= 2000 and M <= 5000
    对于 100%的数据,N <= 20000 and M <= 50000


    这题的题意一开始我没怎么理解,问了别人后知道这其实是一道图论题。

    首先我们看到它要求的全部等效,其实就是求一张强联通图,而这题就是问我们给出的图加几条边可以变成一张强联通图。

    于是就需要用到Tarjan缩点的方法。

    说到Tarjan缩点,我一直也不是很精通,只是了解原理,代码几乎没怎么打过,这道题也就当打模板了。

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    
    #define ll long long
    #define il inline
    #define db double
    
    #define max(a,b) ((a)>(b)?(a):(b))
    
    using namespace std;
    
    il int gi()
    {
    	int x=0,y=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9')
    		{
    			if(ch=='-')
    				y=-1;
    			ch=getchar();
    		}
    	while(ch>='0'&&ch<='9')
    		{
    			x=x*10+ch-'0';
    			ch=getchar();
    		}
    	return x*y;
    }
    
    int head[50045],cnt;
    
    struct edge
    {
    	int to,next;
    }e[50045];
    
    il void add(int from,int to)
    {
    	e[++cnt].next=head[from];
    	e[cnt].to=to;
    	head[from]=cnt;
    }
    
    int low[50045],dfn[50045],in[50045],out[50045];//low记录该点可以往上回溯最远的深度,dfn记录该点深度
    
    int stack[50045],belong[50045];
    
    int n,m,top,coun,num;
    
    bool vis[1000045];
    
    void Tarjan(int x)
    {
    	dfn[x]=low[x]=++num;
    	stack[top++]=x;//入栈
    	vis[x]=1;//标记在栈中
    	int r=head[x];
    	while(r!=-1)
    		{
    			int now=e[r].to;
    			if(!dfn[now])//没被遍历过
    				{
    					Tarjan(now);
    					low[x]=min(low[x],low[now]);//low取最小值
    				}
    			else 
    				if(vis[now])//已经在栈里面了
    					low[x]=min(low[x],dfn[now]);
    			r=e[r].next;
    		}
    	if(low[x]==dfn[x])//该点可以缩  
    		{
    			coun++;
    			int tmp;
    			while(1)
      				{
    					tmp=stack[--top];//把可以缩的缩了
    					belong[tmp]=coun;//记录该点缩了后在哪个点
    					vis[tmp]=0;//出栈
    					if(tmp==x)
    			  			break;
    			  	}
    		}
    }
    
    int main()
    {
    	freopen("set.in","r",stdin);
    	freopen("set.out","w",stdout);
    
    	while(scanf("%d%d",&n,&m)!=EOF)
    		{
    			memset(head,-1,sizeof(head));
    			memset(belong,0,sizeof(belong));
    			memset(in,0,sizeof(in));
    			memset(out,0,sizeof(out));
    			memset(stack,0,sizeof(stack));
    			memset(dfn,0,sizeof(dfn));
    			memset(low,0,sizeof(low));
    			cnt=0;
    			coun=0;
    			num=0;
    
    			int x,y;
    			for(int i=1;i<=m;i++)
    				{
    					x=gi(),y=gi();
    					add(x,y);
    				}
    	
    			for(int i=1;i<=n;i++)
    				if(!dfn[i])
    					Tarjan(i);
    	
    			int rudu=0,chudu=0;
    			for(int i=1;i<=n;i++)
    				{
    					int t=belong[i];
    					int r=head[i];
    					while(r!=-1)
    						{
    							int now=e[r].to;
    							if(belong[now]!=t)
    								{
    									in[belong[now]]++;
    									out[t]++;
    								}
    							r=e[r].next;
    						}
    				}
    
    			for(int i=1;i<=coun;i++)
    				{
    					if(in[i]==0)
    						rudu++;
    					if(out[i]==0)
    						chudu++;
    				}
    	
    			printf("%d
    ",max(rudu,chudu));
    		}
    
    	return 0;
    }
    
    PEACE
  • 相关阅读:
    TCP通信 -C/S中的Socket与ServerSocket
    打印流 -可将数据写入文件/可改变输出方向
    转换流 -解决输入输出时编码格式不统一的问题
    字节/字符缓冲流
    Properties -IO相关的双列集合类
    IO流 -字符输入输出流,以及异常处理方法
    IO流
    线程间的通信
    o(* ̄︶ ̄*)o
    1
  • 原文地址:https://www.cnblogs.com/gshdyjz/p/7656595.html
Copyright © 2011-2022 走看看