BZOJ 4602 HYSBZ 4602 齿轮 DFS 或 BFS 或 并查集

3 篇文章 0 订阅
3 篇文章 0 订阅

题目描述:

现有一个传动系统,包含了N个组合齿轮和M个链条。每一个链条连接了两个组合齿轮u和v,并提供了一个传动比x 

: y。即如果只考虑这两个组合齿轮,编号为u的齿轮转动x圈,编号为v的齿轮会转动y圈。传动比为正表示若编号

为u的齿轮顺时针转动,则编号为v的齿轮也顺时针转动。传动比为负表示若编号为u的齿轮顺时针转动,则编号为v的齿轮会逆时针转动。若不同链条的传动比不相容,则有些齿轮无法转动。我们希望知道,系统中的这N个组合齿轮能否同时转动。

Input

有多组数据,第一行给定整数T,表示总的数据组数,之后依次给出T组数据。每一组数据的第一行给定整数N和M,表示齿轮总数和链条总数。之后有M行,依次描述了每一个链条,其中每一行给定四个整数u,v,x和y,表示只考虑这一组联动关系的情况下,编号为u的齿轮转动x圈,编号为v的齿轮会转动y圈。请注意,x为正整数,而y为非零整数,但是y有可能为负数。

T<=32,N<=1000,M<=10000且x与y的绝对值均不超过100

Output

输出T行,对应每一组数据。首先应该输出标识这是第几组数据,参见样例输出。之后输出判定结果,如果N个组合

齿轮可以同时正常运行,则输出Yes,否则输出No。

Sample Input

2

33

1 2 3 5

2 3 5 -7

1 3 3 -7

3 3

1 2 3 5

2 3 5 -7

1 3 3 7

Sample Output

Case #1: Yes

Case #2: No

看到这个题目,我第一反应是 “传递” ,想了想,不是传递闭包,思路大概就是不断地把齿轮加入当前搜索到的集合,然后根据转的圈数之比,判断是否非法。搜索完后,都不非法,说明可以转。

根据题意,齿轮看做点,链条看作边,因为是相互之间都满足转动的比,所以建立无向图。

#include <bits/stdc++.h>
using namespace std ;
const double eps = 1e-7 ;
int head[1005] , k ;
int To[20005] , Next[20005] ;
double cost[20005] , dis[1005] ;     // dis 是当前相对的转动比
int ans ;

void Add( int u , int v , double w ){
	To[k] = v , cost[k] = w , Next[k] = head[u] , head[u] = k++ ;
}

int DFS( int u , int pre ){
	for( int i = head[u] ; i != -1 ; i = Next[i] ){
		int v = To[i] ;
		if( v == pre ) continue ;
		if( fabs( dis[v] - 0 ) > eps ){                           // 如果没走过这个点
			if( fabs( dis[v] - dis[u] * cost[i] ) > eps )     // 相对转动比差距大
				return 0 ;
		} else {
			dis[v] = dis[u] * cost[i] ;                       // 没走过 v 
			if( !DFS( v , u ) ) 
				return 0 ;
		} 
	}
	return 1 ;
}

int main(){
	// freopen( "HYSBZ 4602.txt" , "r" , stdin ) ;
	int cases , N , M , u , v , x , y ;
	scanf( "%d" , &cases ) ;
	for( int t = 1 ; t <= cases ; ++t ){
		k = ans = 0 ;
		memset( head , -1 , sizeof( head ) ) ;
		memset( dis , 0 , sizeof( dis ) ) ;
		scanf( "%d%d" , &N , &M ) ;
		for( int i = 1 ; i <= M ; ++i ){
			scanf( "%d%d%d%d" , &u , &v , &x , &y ) ;
			Add( u , v , (double)x/y ) ;
			Add( v , u , (double)y/x ) ;
		}
		for( int i = 1 ; i <= N ; ++i ){
			if( fabs( dis[i] - 0 ) > eps ) continue ;
			dis[i] = 1.00 ;                            // 每次搜索第一个点的相对的转动设为 1
			if( !DFS( i , -1 ) ){              
				ans = 1 ; break ;                  // 有一个不符合
			}
		}
		printf( "Case #%d: " , t ) ;
		printf( ans ? "No\n" : "Yes\n" ) ;
	}
	return 0 ;
}
既然是搜索,不断把点加入集合,当然也可以用 BFS 了。思路一样。
#include <bits/stdc++.h>
using namespace std ;
const double eps = 1e-7 ;
int head[1005] , k ;
int To[20005] , Next[20005] ;
int father[1005] ;
double cost[20005] , dis[1005] ;
int ans , N , M ;

void Add( int u , int v , double w ){
	To[k] = v , cost[k] = w , Next[k] = head[u] , head[u] = k++ ;
}

int BFS(){
	queue<int> Q ;
	for( int i = 1 ; i <= N ; ++i ){
		if( father[i] ) 
			continue ;       
		father[i] = i ;            // 继续遍历其他的连通分量
		Q.push( i ) ;
		dis[i] = 1.00 ;            // 别忘记初始化
		while( !Q.empty() ){
			int u = Q.front() ;
			Q.pop() ;
			for( int i = head[u] ; i != -1 ; i = Next[i] ){
				int v = To[i] ;
				if( v == father[u] )
					continue ;
				if( father[v] ){
					if( fabs( dis[v] - dis[u] * cost[i] ) > eps )
						return 0 ;
				} else {
					dis[v] = dis[u] * cost[i] ;
					father[v] = u ;
					Q.push( v ) ;
				}
			}
		}
	}
	return 1 ;
}

int main(){
	// freopen( "HYSBZ 4602.txt" , "r" , stdin ) ;
	int cases , u , v , x , y ;
	scanf( "%d" , &cases ) ;
	for( int t = 1 ; t <= cases ; ++t ){
		k = ans = 0 ;
		memset( father , 0 , sizeof( father ) ) ;
		memset( head , -1 , sizeof( head ) ) ;
		memset( dis , 0 , sizeof( dis ) ) ;
		scanf( "%d%d" , &N , &M ) ;
		for( int i = 1 ; i <= M ; ++i ){
			scanf( "%d%d%d%d" , &u , &v , &x , &y ) ;
			Add( u , v , (double)x/y ) ;
			Add( v , u , (double)y/x ) ;
		}
		printf( "Case #%d: " , t ) ;
		printf( BFS() ? "Yes\n" : "No\n" ) ;
	}
	return 0 ;
}

每次把点加入集合,就很容易联想到并查集。如果,每次把一个点加入集合,然后计算 它相对 “并查集的根” 转的圈数。如果点就在集合中,就检验是否出现矛盾。

#include <bits/stdc++.h>
using namespace std ;
const double eps = 1e-7 ;
int N , M , fa[1005] ;
double dis[1005] ;

int find( int u ){
	if( u == fa[u] ) return u ;
	int t = fa[u] ;                 // 保存上一次的父节点
	fa[u] = find( fa[u] ) ;
	dis[u] *= dis[t] ;              // 回溯的时候, 从根一直更新整条路径的相对圈数
	return fa[u] ;
}

int main(){
	// freopen( "HYSBZ 4602.txt" , "r" , stdin ) ;
	int cases , u , v , x , y ;
	scanf( "%d" , &cases ) ;
	for( int t = 1 ; t <= cases ; ++t ){
		scanf( "%d%d" , &N , &M ) ;
		for( int i = 1 ; i <= N ; ++i )
			fa[i] = i , dis[i] = 1.00 ;        
		int OK = 1 ;
		for( int i = 1 ; i <= M ; ++i ){
			scanf( "%d%d%d%d" , &u , &v , &x , &y ) ;
			double cost = (double)y/x ;                     // cost 是 v 相对 u 的圈数
			int a = find( u ) ;
			int b = find( v ) ;
			if( a == b ){                                  // u 和  v 在同一集合, 检验是否冲突
				if( fabs( dis[v] - dis[u] * cost ) > eps )
					OK = 0 ; 
			} else {                                       // u 和 v 不在同一集合, 就合并
				fa[b] = a ;
				dis[b] = ( dis[u] * cost ) / dis[v]  ;      // 更新被合并的集合的根的圈数
			}
		}
		printf( "Case #%d: " , t ) ;
		printf( OK ? "Yes\n" : "No\n" ) ;
	}
	return 0 ;
}
其实,上面的DFS, BFS, 并查集 ,我感觉都是利用了“连通分量” , 只要两个齿轮在同一连通分量内,或者说同一个齿轮系统中,就要检验它们的转动是否矛盾。如果暂时,两个齿轮不在同一连通分量,但是加个链条,就属于同一连通分量了,然后更新被合并的部分的转动。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值