牛客算法周周练4 - [SDOI2016]齿轮(建图DFS、带权并查集)
牛客算法周周练4 - [SDOI2016]齿轮(建图DFS、带权并查集)
链接:https://ac.nowcoder.com/acm/contest/5505/A
来源:牛客网
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
输入描述:
有多组数据,第一行给定整数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
输出描述:
输出T行,对应每一组数据。首先应该输出标识这是第几组数据,参见样例输出。之后输出判定结果,如果N个组合齿轮可以同时正常运行,则输出Yes,否则输出No。
输入
2 3 3 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
输出
Case #1: Yes Case #2: No
解法一(建图DFS):
根据题意,建立一个无向图,每条边多记录一个x和y。
我们令起始点u的w值为1.0,然后根据x和y去求下一个点v的w值,中途判断是否矛盾即可。
遍历所有节点,没访问过的w初始化为1.0,然后DFS,如果再次DFS到这个点v,判断w[v]是否等于w[u]*(x/y),不等直接No就行了。
注意这个图可能不连通
具体的看代码吧:
1 #include <bits/stdc++.h> 2 typedef long long LL; 3 #define pb push_back 4 const int INF = 0x3f3f3f3f; 5 const double eps = 1e-8; 6 const int mod = 1e9+7; 7 const int maxn = 1e5+10; 8 using namespace std; 9 10 struct edge 11 { 12 int to; 13 int x, y; 14 int next; 15 }E[maxn];//注意边的条数 16 int head[maxn], tot; 17 void add(int u,int v,int x,int y) 18 { 19 E[tot].to=v; 20 E[tot].x=x; E[tot].y=y; 21 E[tot].next=head[u]; 22 head[u]=tot++; 23 } 24 25 int n,m; 26 int ok; 27 int vis[maxn]; 28 double w[maxn]; 29 void init() 30 { 31 tot = 0; 32 ok = 1; 33 memset(head,-1,sizeof(head)); 34 memset(vis,0,sizeof(vis)); 35 } 36 void DFS(int u, int fa) 37 { 38 for(int i=head[u];i!=-1;i=E[i].next) 39 { 40 int v=E[i].to, x=E[i].x, y=E[i].y; 41 if(v==fa) continue; 42 if(vis[v]) 43 { 44 if(fabs(w[v] - w[u]*(1.0*x/y))>eps)//出现矛盾 45 { 46 ok=0; 47 return ; 48 } 49 } 50 else 51 { 52 vis[v]=1; w[v]=w[u]*(1.0*x/y); 53 DFS(v, u); 54 } 55 } 56 } 57 58 int main() 59 { 60 #ifdef DEBUG 61 freopen("sample.txt","r",stdin); //freopen("data.out", "w", stdout); 62 #endif 63 64 int T; 65 scanf("%d",&T); 66 for(int k=1;k<=T;k++) 67 { 68 printf("Case #%d: ",k); 69 scanf("%d %d",&n,&m); 70 init(); 71 for(int i=1;i<=m;i++) 72 { 73 int u,v,x,y; 74 scanf("%d %d %d %d",&u,&v,&x,&y); 75 add(u,v,x,y); 76 add(v,u,y,x); 77 } 78 for(int i=1;i<=n&&ok;i++) 79 { 80 if(!vis[i]) 81 { 82 vis[i]=1; w[i]=1.0; 83 DFS(i,-1); 84 } 85 } 86 printf(ok? "Yes\n":"No\n"); 87 } 88 89 return 0; 90 }
解法二(带权并查集):
from:Inf_Voltage
根据题意,我们发现如果图中没有环一定满足题意,如果有环且边权之积为1时满足条件,否则一定不满足
很好理解,我们任意取环上一点使之转动一周,经过环的传动回到自己这里一定还是只转一周
处理点之间的关系,并查集是个不错的选择,本题不仅要处理是否成环,还要求出环上的边权积,带权并查集则能够很好的完成这个任务
对于并查集,我们定义fa[i]表示节点i的祖先, w[i]表示从节点i到fa[i]的传动比之积
对于每个链条(u, v, x, y),如果(u, v)在一 个联通块中, 显然连了u-v后会成环,我们用K=w[u]/w[v]得到u-v的链上的传动比之积,如果K==x/y则满足条件,否则整个传动装置不合法
如果(u, v)不在一个联通块中, 我们则需要在fa[u]-fa[v]之间连一 条边权t边,使得t*w[u]/w[v]=x/y 解方程得到t=(x*w[v])/(y*w[u])
看看矢量图(公式编出来的):(y/x)*w[u]*t=w[v],即t=(x*w[v])/(y*w[u])
需要特别注意的是精度问题,eps=1e-10能过
1 #include <bits/stdc++.h> 2 typedef long long LL; 3 #define pb push_back 4 const int INF = 0x3f3f3f3f; 5 const double eps = 1e-8; 6 const int mod = 1e9+7; 7 const int maxn = 1e5+10; 8 using namespace std; 9 10 int n,m; 11 int ok; 12 double w[maxn]; 13 14 int fa[maxn]; 15 void init(int n) 16 { 17 ok = 1; 18 for(int i=1;i<=n;i++) 19 fa[i]=i, w[i]=1.0; 20 } 21 int Find(int x)//带权并查集 22 { 23 if(x!=fa[x]) 24 { 25 int t=fa[x]; 26 fa[x]=Find(fa[x]); 27 w[x]*=w[t]; 28 } 29 return fa[x]; 30 } 31 32 int main() 33 { 34 #ifdef DEBUG 35 freopen("sample.txt","r",stdin); //freopen("data.out", "w", stdout); 36 #endif 37 38 int T; 39 scanf("%d",&T); 40 for(int k=1;k<=T;k++) 41 { 42 printf("Case #%d: ",k); 43 scanf("%d %d",&n,&m); 44 init(n); 45 for(int i=1;i<=m;i++) 46 { 47 int u,v,x,y; 48 scanf("%d %d %d %d",&u,&v,&x,&y); 49 if(ok==0) continue; 50 int uu=Find(u), vv=Find(v); 51 if(uu!=vv) 52 { 53 fa[uu]=vv; 54 w[uu]*=x*w[v]/(y*w[u]); 55 } 56 else 57 { 58 if(fabs(w[u]/w[v]-x*1.0/y)>eps) ok=0; 59 } 60 } 61 printf(ok? "Yes\n":"No\n"); 62 } 63 64 return 0; 65 }
-