首页 > 代码库 > [Sdoi2016]齿轮

[Sdoi2016]齿轮

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

By AHdoc命题 鸣谢Loi_DQS上传

【民间做法】: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;}

 

[Sdoi2016]齿轮