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
  • 相关阅读:
    CodeForces 19D Points (线段树+set)
    FZU 2105 Digits Count
    HDU 5618 Jam's problem again(三维偏序,CDQ分治,树状数组,线段树)
    HDU 5634 Rikka with Phi (线段树)
    Java实现 蓝桥杯 算法提高 转圈游戏(暴力快速幂)
    Java实现 蓝桥杯 算法提高 转圈游戏(暴力快速幂)
    Java实现 蓝桥杯 算法提高 转圈游戏(暴力快速幂)
    Java实现 蓝桥杯 算法提高VIP Substrings(暴力)
    Java实现 蓝桥杯 算法提高VIP Substrings(暴力)
    Java实现 蓝桥杯 算法提高VIP Substrings(暴力)
  • 原文地址:https://www.cnblogs.com/gshdyjz/p/7656595.html
Copyright © 2011-2022 走看看