NC20583 [SDOI2016]齿轮

题目链接

题目

题目描述

现有一个传动系统,包含了N个组合齿轮和M个链条。每一个链条连接了两个组合齿轮u和v,并提供了一个传动比x : y。

即如果只考虑这两个组合齿轮,编号为u的齿轮转动x圈,编号为v的齿轮会转动y圈。传动比为正表示若编号为u的齿轮顺时针转动,则编号为v的齿轮也顺时针转动。传动比为负表示若编号为u的齿轮顺时针转动,则编号为v的齿轮会逆时针转动。若不同链条的传动比不相容,则有些齿轮无法转动。我们希望知道,系统中的这N个组合齿轮能否同时转动。

输入描述

有多组数据,第一行给定整数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。

示例1

输入

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

题解

方法一

知识点:并查集。

用带权并查集维护齿轮之间的关系,用权值表示根节点转一圈,这个节点转的圈数。

路径压缩,将父节点到根节点的权值乘以自己的权值。

集合合并,对于节点 \(x\)\(y\) 以及其根节点 \(rx\)\(ry\) ,将 \(rx\) 合并到 \(ry\) 需要得到 \(rx\) 的新权值,即得到 \(rx\)\(ry\) 的传动比,有:

\[w_{rx}' = \frac{1}{w_{x}} \cdot \frac{x}{y} \cdot \frac{w_{y}}{1} \cdot 1 \]

即四个齿轮的三个传动比相乘得到 \(rx\)\(ry\) 的传动比再将 \(ry\) 圈数设为 \(1\) ,于是 \(w_{rx}\) 就等于 \(rx\)\(ry\) 的传动比乘以 \(ry\) 的圈数 \(1\)

如果新加关系的两个点已经在一个关系集合中,那就检验是否合法:

\[\frac{w_{x}}{w_{y}} = \frac{x}{y} \]

两者传动比是否相等,相等则合法。

注意精度问题,相等用小于误差表示。

时间复杂度 \(O(n + m\log n)\)

空间复杂度 \(O(n)\)

方法二

知识点:DFS,图论。

和并查集思路差不多,但建图时要建无向图,因为需要直到可能遍历时走的是反向的。给起点权值赋为 \(1\) ,其他节点根据传动比赋值,上一个节点乘以这个方向的传动比的倒数的结果即是这个点实际转多少圈。

如果遇到遍历到一个访问过的节点,那就判断实际权值和目前算出来的权值是否相等。

时间复杂度 \(O(n+m)\)

空间复杂度 \(O(n+m)\)

代码

方法一

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int n, m;
int fa[10007];
double w[10007];

int find(int x) {
    if (fa[x] == x) return x;
    int pre = fa[x];
    fa[x] = find(fa[x]);
    w[x] *= w[pre];
    return fa[x];
}

bool merge(int x, int y, double r) {
    int rx = find(x);
    int ry = find(y);
    if (rx == ry)
        return abs(w[x] / w[y] - r) < 1e-6;
    fa[rx] = ry;
    w[rx] = 1 / w[x] * r * w[y];
    return true;
}

bool solve() {
    cin >> n >> m;
    for (int i = 1;i <= n;i++) fa[i] = i, w[i] = 1;
    bool flag = true;
    for (int i = 0;i < m;i++) {
        int u, v;
        double x, y;
        cin >> u >> v >> x >> y;
        if (!flag) continue;
        flag &= merge(u, v, x / y);
    }
    return flag;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    for (int i = 1;i <= t;i++) {
        cout << "Case #" << i << ": ";
        if (!solve()) cout << "No" << '\n';
        else cout << "Yes" << '\n';
    }
    return 0;
}

方法二

#include <bits/stdc++.h>
#define ll long long

using namespace std;

int n, m;
struct edge {
    int to, nxt;
    double w;
}e[10007 << 1];
int h[1007], cnt;
double vis[1007];

void add(int u, int v, double w) {
    e[cnt].to = v;
    e[cnt].w = w;
    e[cnt].nxt = h[u];
    h[u] = cnt++;
}

bool dfs(int u) {
    for (int i = h[u];~i;i = e[i].nxt) {
        int v = e[i].to;
        if (vis[v]) {
            if (abs(vis[v] - vis[u] / e[i].w) > 1e-6) return false;
        }
        else {
            vis[v] = vis[u] / e[i].w;
            if (!dfs(v)) return false;
        }
    }
    return true;
}

bool solve() {
    cin >> n >> m;
    for (int i = 1;i <= n;i++) h[i] = -1, vis[i] = 0;
    cnt = 0;
    for (int i = 0;i < m;i++) {
        int u, v;
        double x, y;
        cin >> u >> v >> x >> y;
        add(u, v, x / y);
        add(v, u, y / x);
    }
    vis[1] = 1;
    return dfs(1);
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    for (int i = 1;i <= t;i++) {
        cout << "Case #" << i << ": ";
        if (!solve()) cout << "No" << '\n';
        else cout << "Yes" << '\n';
    }
    return 0;
}
posted @ 2022-07-16 16:01  空白菌  阅读(88)  评论(0编辑  收藏  举报