[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;
}
时间: 2024-11-06 22:16:40

[Sdoi2016]齿轮的相关文章

4602: [Sdoi2016]齿轮

4602: [Sdoi2016]齿轮 Description 现有一个传动系统,包含了N个组合齿轮和M个链条.每一个链条连接了两个组合齿轮u和v,并提供了一个传动比x : y.即如果只考虑这两个组合齿轮,编号为u的齿轮转动x圈,编号为v的齿轮会转动y圈.传动比为正表示若编号 为u的齿轮顺时针转动,则编号为v的齿轮也顺时针转动.传动比为负表示若编号为u的齿轮顺时针转动,则编号为v 的齿轮会逆时针转动.若不同链条的传动比不相容,则有些齿轮无法转动.我们希望知道,系统中的这N个组合齿 轮能否同时转动.

【bzoj4602】[Sdoi2016]齿轮

dfs,连边,边权为比值,赋值搜索,遇到矛盾时退出 #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> using namespace std; typedef long long LL; typedef double DB; #define eps 1e-8 #defin

[bzoj4602] [Sdoi2016]齿轮

记忆化搜索,强行double可过...数据简直.. 1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cmath> 5 #include<cstdlib> 6 #define ld double 7 using namespace std; 8 const int maxn=1023,maxm=10023; 9 const ld eps=1e-8; 10

BZOJ4602: [Sdoi2016]齿轮 DFS 逆元

这道题就是一个DFS,有一篇奶牛题几乎一样.但是这道题卡精度. 这道题网上的另一篇题解是有问题的.取对数这种方法可以被轻松卡.比如1e18 与 (1e9-1)*(1e9+1)取对数根本无法保证不被卡精度.所以我们需要换一个方法. 我们取一个大质数,在这个质数的模意义下进行运算:乘法是乘法,除法变成乘逆元,负数加一个质数.这种方法虽然可能冲突,但是与数据无关. #include<cstdio> using namespace std ; struct edge { int p ; int a ;

bzoj 4602: [Sdoi2016]齿轮

传送门 题解: 一个简单的dfs或并查集即可搞定. dfs做法:建出图来以后,由一个节点跑一遍,如果儿子节点没有被遍历过,dfs此节点,否则,判断他俩的比例与他俩到根节点的比例之比是否相等. 对于判断两节点的比例,double可以安全存下. 我怕炸精度(其实不会,感觉自己跟个zz一样),于是想不用double,用个pair存下分子分母.后来觉得导起来太麻烦,于是用逆元求(越来越傻了). 逆元不需要太大的素数,大了会T,小了会WA. bzoj上测试了5个素数:1000000007,66191,73

待 题表

题表 达哥终极杂题表Bzoj2839 hdu6021 Codeforces 804DBzoj2248 hdu5575 Codeforces 786CBzoj2013 bzoj2676 Codeforces 803CBzoj2386 bzoj3782 Codeforces 813DBzoj2699 cogs1667 Codeforces 814DBzoj4798 bzoj2064 Codeforces 814EBzoj4639 bzoj3505 Codeforces 815ABzoj4417 bz

联赛之前的题表(已完成)汇总(可能有遗漏)

联赛之前的搞搞(其实是懒得分类) 博弈论 poj3537 poj1704 hdu5996两个插头 HDU1693 Eat the Trees COGS1283. [HNOI2004] 邮递员kdtree板子1941: [Sdoi2010]Hide and Seek旋转卡壳 pj2187凸包 cogs896 bzoj2829 信用卡凸包莫比乌斯反演基础 bzoj 4173 zhao gui lv bzoj 3529 mobiwus bzoj 4407 mobiwus bzoj 2818 mobiw

[qbzt寒假] 并查集

并查集: \(Kruscal\),\(Tarjan\)求\(LCA\) 分类并查集:食物链,团伙(敌人的敌人是我的朋友) 带权并查集:\(SDOI2016\)齿轮(可用 int father(int x) { return fa[x]==x?x:fa[x]=father(f[x]); } Luogu3101 滑雪等级[] 建边:任意相邻两格子之间建边,权值为海拔差 将边排序,从小往大一个一个往里加,当一个并查集内部有起点,并且大小(点数)>=T,这里面所有的起点的D=最后加入的边 #includ

BZOJ 4517: [Sdoi2016]排列计数 错排+逆元

4517: [Sdoi2016]排列计数 Description 求有多少种长度为 n 的序列 A,满足以下条件: 1 ~ n 这 n 个数在序列中各出现了一次 若第 i 个数 A[i] 的值为 i,则称 i 是稳定的.序列恰好有 m 个数是稳定的 满足条件的序列可能很多,序列数对 10^9+7 取模. Input 第一行一个数 T,表示有 T 组数据. 接下来 T 行,每行两个整数 n.m. T=500000,n≤1000000,m≤1000000 Output 输出 T 行,每行一个数,表示