luoguP4079 [SDOI2016]齿轮 [逆元][dfs]

P4079 [SDOI2016]齿轮 link

在这里插入图片描述
在这里插入图片描述

思路:
嗯这题据说double会爆精度,暴力乘会爆longlong。。
方法是用dfs,正常算比例是乘上vt除以vf(vt,vf定义见代码),
然后因为要取模所以除法变成算逆元
然后如果是个无环图那就应该没问题,
如果是个有环图(一个点能被dfs到不止一次)那就特判一下,
看两次遍历的结果是否一致,不一致说明不行

#include <cstdio>
#include <cstring>
#include <vector>
#define ll long long

using namespace std;

const int maxn = 3e3 + 5;
const int maxm = 2e4 + 5;
struct Edge
{
    int from, to;
    ll vt, vf;
    Edge() {}
    Edge(int f, int t, ll _vt, ll _vf) {from = f, to = t, vt = _vt, vf = _vf;}
}e[maxm];
struct Edge1
{
    int from, to;
    ll c;
    Edge1() {}
    Edge1(int f, int t, ll _c) {from = f, to = t, c = _c;}
};
vector <Edge> edge[maxn];
int n, m;
ll node[maxn];
bool vis[maxn];
//本题有负数,取模需要对其加到正后再取,C++负数取模比较诡异

inline ll mod(ll a, ll p)
{
    return ((a%p) + p) % p;
}

void Init()
{
    for (int i = 1; i <= n; i++)
        edge[i].clear();
    memset(vis, 0, sizeof(vis));
    memset(node, 0, sizeof(node));
}

//快速幂,用于算逆元
ll getn(ll a, ll p)
{
    ll ans = 1, b = mod(a, p);
    ll z = p - 2;
    while (z != 0)
    {
        if (z & 1)
        {
            ans *= b;
            ans %= p;
        }
        b *= b;
        b %= p;
        z >>= 1;
    }
    return ans;
};

bool dfs(int u, ll z, ll p)
{
    vis[u] = 1, node[u] = z;
    bool ands = 1;
    bool gg[maxn] = { 0 };
    int sz = edge[u].size();
    for (int i = 0; i < sz; i++)
    {
        int v = edge[u][i].to;
        //已经遍历的情况,并且两次遍历结果一致,说明该点暂时可行
        if (vis[v] && node[v] == (z*edge[u][i].vf % p)*edge[u][i].vt % p) gg[i] = 1;
        //如果没被遍历过就往下遍历
        else if (!vis[v])
            gg[i] = dfs(v, (z*edge[u][i].vf % p)*edge[u][i].vt % p, p);
    }
    //必须所有子结点点都可行才可行
    for (int i = 0; i < sz; i++)
        if (!gg[i])
            ands = 0;
    return ands;
}

bool work_work(ll p)
{
    Init();
    for (int i = 1; i <= m; i++)
    {
        edge[e[i].from].push_back(Edge(e[i].from, e[i].to, getn(e[i].vf, p), mod(e[i].vt, p)));
        edge[e[i].to].push_back(Edge(e[i].to, e[i].from, getn(e[i].vt, p), mod(e[i].vf, p)));
    }
    int flag = 1;
    //图不一定连通,需要判定每一个连通的部分是否可行
    for (int i = 1; i <= n; i++)
        if (!vis[i])
            flag = ((dfs(i, 1, p) == true) ? flag : false);
    return flag;
}

void work(int i)
{
    memset(e, 0, sizeof(e));
    scanf("%d%d", &n, &m);
    for (int a = 1; a <= m; a++)
        scanf("%d%d%lld%lld", &e[a].from, &e[a].to, &e[a].vf, &e[a].vt);
    bool ans = (work_work(1e9 + 7) && work_work(1e9 + 9));
    printf("Case #%d: ", i);
    if (ans) printf("Yes\n");
    else     printf("No\n");
}

int main()
{
    int t;
    scanf("%d", &t);
    for (int i = 1; i <= t; i++) work(i);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值