[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

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

 

posted @ 2017-05-14 15:13  神犇(shenben)  阅读(671)  评论(0编辑  收藏  举报