题目描述:
现有一个传动系统,包含了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, 并查集 ,我感觉都是利用了“连通分量” , 只要两个齿轮在同一连通分量内,或者说同一个齿轮系统中,就要检验它们的转动是否矛盾。如果暂时,两个齿轮不在同一连通分量,但是加个链条,就属于同一连通分量了,然后更新被合并的部分的转动。