4602: [Sdoi2016]齿轮
Time Limit: 10 Sec Memory Limit: 512 MB
Submit: 613 Solved: 324
[Submit][Status][Discuss]
Description
现有一个传动系统,包含了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
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
Sample Output
Case #1: Yes
Case #2: No
HINT
Source
【民间做法】:DFS
//对于每组齿轮(u, v)连边,权值为y/x(反向边x/y) //dfs 判断矛盾 #include<cmath> #include<cstdio> #include<cstring> #include<iostream> using namespace std; typedef double real; inline void read(int &x){ register char ch=getchar();int f=1;x=0; while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();} x*=f; } const int N=1e5+5; const real eps=1e-6; struct edge{int v,next;real w;}e[N<<1];int tot,head[N]; int T,n,m,cas; bool vis[N],flag; real val[N]; inline void add(int x,int y,real z){ e[++tot].v=y;e[tot].w=z;e[tot].next=head[x];head[x]=tot; } inline bool dfs(int x,real sp){ vis[x]=1; val[x]=sp; for(int i=head[x];i;i=e[i].next){ if(!vis[e[i].v]){ if(dfs(e[i].v,sp*e[i].w)) return 1; } else{ if(fabs(val[e[i].v]-sp*e[i].w)>eps) return 1; } } return 0; } inline void clr(){ tot=0;flag=0; memset(head,0,(n+1)<<2); memset(vis,0,(n+1)<<2); } int main(){ for(read(T);T--;clr()){ read(n);read(m); for(int i=1,u,v,x,y;i<=m;i++){ read(u);read(v); read(x),read(y); add(u,v,(real)y/(real)x); add(v,u,(real)x/(real)y); } for(int i=1;i<=n;i++) if(!vis[i]){ if(dfs(i,1)){ flag=1;break; } } printf("Case #%d: %s\n",++cas,flag?"No":"Yes"); } return 0; }
【官方做法】:加权并查集
首先可以看出这个齿轮的转动关系是具有传递性的,如果知道x和y的关系,也知道y和z的关系,就可以推出x和z的关系。具有这种关系的东西一般都可以用加权并查集来搞。对于每个元素给它维护一个dis[x],表示的含义是如果x的代表元素转动1圈,x要转动几圈。每次读入一个限制如果x和y不在同一个集合里就进行合并。合并的时候先找到x的代表元素r1和y的代表元素r2,然后我们要把r2合并到r1上。要进行合并就要知道r1转一圈的时候r2转几圈。设从读入的信息可以知道x转一圈的时候y转w圈,那么如果r1转了一圈,y就转了dis[x]∗w圈;而当y转了dis[x]∗w圈的时候,r2转了dis[x]∗w/dis[y]圈。关系如下图:
如果读入的x和y在一个集合里,那么就可以用维护的信息算出x转了一圈的时候y转了几圈,和读入的信息进行比对就可以判定是否发生矛盾。
这个数据范围看起来好像很小的样子。。但是如果它出上n-1个信息,每次都是当齿轮i转动1圈的时候齿轮i+1转动100圈,那么最后就会发现当齿轮1转动一圈的时候齿轮n会转动100n圈。。这样的话就不能直接用int或者long
long或者什么类似的东西来维护加权并查集的dis了。。用分解质因数似乎是比较稳的方法,但是听说用long double还是什么类似的东西也能过?
#include<cmath> #include<cstdio> using namespace std; typedef double real; inline void read(int &x){ register char ch=getchar();int f=1;x=0; while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();} x*=f; } const int N=1e5+5; const real eps=1e-6; int T,n,m,cas;bool flag; int fa[N];real dis[N]; int find(int x){ int t; if(fa[x]!=x) t=find(fa[x]),dis[x]*=dis[fa[x]],fa[x]=t; return fa[x]; } int main(){ for(read(T);T--;flag=0){ read(n);read(m); for(int i=1;i<=n;i++) fa[i]=i,dis[i]=1; for(int i=1,r1,r2,u,v,x,y;i<=m;i++){ read(u);read(v);read(x);read(y); r1=find(u);r2=find(v); if(r1==r2){ if(fabs(dis[v]/dis[u]-(real)y/(real)x)>eps){ flag=1;break; } } else{ fa[r2]=r1; dis[r2]=(dis[u]*y)/(dis[v]*x); } } printf("Case #%d: %s\n",++cas,flag?"No":"Yes"); } return 0; }