刷题记录:牛客NC20583[SDOI2016]齿轮

传送门:牛客

题目描述:

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

不愧是牛客上评级为4星的题目,感觉思维难度不低,还带有亿点点细节(写了两种方法耗时一个晚上)

该题主要有并查集以及BFS两种方法

我们读完题目之后肯定会发现传送比是具有传递性质的,假设我们假设A与B的传动比为X(A转一圈B转X圈),如果此时我们又有B与C的传动比为Y,那么此时显然我们的A与C的传动比为X*Y.我们再假设一下假设我们的A与C不只是靠B来传递的,A与C之间还有一个链条专门来连接的,那么此时我们的间接的传动比和我们的直接的传动比应该是相等的才对(这里的相等包括了正负性),不然的话肯定是有冲突的

想完1中的判断条件之后我们就应该有了一种模糊的一些思路了.似乎这道题好像是需要我们进行判环的一种操作.一想到判环,我们是不是就想到了并查集??或者BFS直接判环??,当然在这道题中我们两者都是可以做的,其中并查集的速度比BFS快了三倍左右

首先是我们的并查集做法:

  1. 我们得使用带权并查集来完成这道题,我们对于每一个并查集还维护一个g数组,用来存储自己与祖先之间的传动比,对于每一个加入的两个点,我们判断一下这两者的父亲是不是同一个(也就是本来是不是在同一条链中),假设原本是在同一条链中的话,那么此时因为这条边就形成了一个环,那么此时就有可能会造成冲突了.我们判断一下我们的传送比是不是一样的即可.假设我们原本不是在同一条链中的话,此时我们需要将他们加入到同一个并查集中,但是我们此时只需要改变我们的两个父亲的值即可,这是肯定会有疑问了,我们只改变了两个并查集的父亲的值,我们此时其他的值怎么不用改变呢,那是因为我们在find函数之中动了一些手脚
nt find(int a) {
	if(a==fa[a]) return a;
	int temp=find(fa[a]);
	g[a]*=g[fa[a]];//看到这一步了吗,这句代码的位置是重要的
	return fa[a]=temp;
}

上面那一句的代码的理解对于掌握带权并查集十分重要,建议手模推一下理解.对于我们的边我们当时是只改了父亲的值,但是当我们在跑路径压缩时,我们此时每一个点的儿子其实是没有更改的(我们是在跑路径压缩时改的父亲),所以此时我们每一个点会在每一次的递归之中会继承我们的原本的父亲也就是说我们的值也回一层一层的被赋值过来,感觉是不是很妙??,这就是算法的魅力

注意点:注意double可能会丢失精度,所以我们需要使用eps来进行模糊判断

下面是具体的代码部分:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <string.h>
#include <stack>
#include <deque>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define root 1,n,1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
#define ll_maxn 0x3f3f3f3f3f3f3f3f
const double eps=1e-8;
int T;
int n,m;
int fa[maxn];
double g[maxn];
int find(int a) {
	if(a==fa[a]) return a;
	int temp=find(fa[a]);
	g[a]*=g[fa[a]];
	return fa[a]=temp;
}
int main() {
	T=read();int ans=0;
	while(T--) {
		n=read();m=read();int flag=0;
		for(int i=1;i<=n;i++) {
			fa[i]=i;g[i]=1;
		}
		int u,v,x,y;
		for(int i=1;i<=m;i++) {
			u=read();v=read();x=read();y=read();
			int fa_u=find(u),fa_v=find(v);
			if(fa_u==fa_v) {
				if(abs(g[v]/g[u]-(double)y/(double)x)>1e-5) flag=1;
			}else {
				fa[fa_v]=fa_u;
				g[fa_v]=g[fa_v]*g[u]*(double)y/(double)x/g[v];
			}
		}
		if(flag==1) printf("Case #%d: No\n",++ans);
		else printf("Case #%d: Yes\n",++ans);
	}
	return 0;
}

下面是BFS的做法:

BFS的做法与并查集的做法大体是相通的,就是使用BFS不断的往周围去搜索,如果搜索到的编号是之前已经搜索过的话,就判断一下两者的传送比是否符合即可.对于传送比,我们可以以其建边,以后只要维护每一个点的传动比的即可(我们需要枚举每一个端点,跟并查集的判环有一点像)

注意点:注意double可能会丢失精度,所以我们需要使用eps来进行模糊判断

下面是具体的代码部分:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <string.h>
#include <stack>
#include <deque>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define root 1,n,1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
#define ll_maxn 0x3f3f3f3f3f3f3f3f
const double eps=1e-8;
int T;
struct Node{
	int u,v;
	double w;
};
vector<Node>edge[maxn];
int vis[maxn];double ans[maxn];
struct Node2{
	int u;double w;
};
int BFS(int s){
	if(vis[s]) return true;
	queue<Node2>q;
	q.push({s,1});ans[s]=1;
	while(!q.empty()) {
		Node2 f=q.front();q.pop();
		int u=f.u;
		if(vis[u]) continue;
		vis[u]=1;
		for(int i=0;i<edge[u].size();i++) {
			int v=edge[u][i].v;
			if(!vis[v]) {
				ans[v]=edge[u][i].w*f.w;
				q.push({v,ans[v]});
			}else {
				if(abs(ans[v]-edge[u][i].w*f.w)>1e-5) return false;
			}
		}
	}
	return true;
}
int Ans=0;
int main() {
	int u,v,x,y;
	T=read();int n,m;
	while(T--) {
		int flag=0;
		memset(ans,0,sizeof(ans));
		memset(vis,0,sizeof(vis));
		n=read();m=read();
		for(int i=1;i<=n;i++) edge[i].clear();
		for(int i=1;i<=m;i++) {
			u=read();v=read();x=read();y=read();
			edge[u].push_back({u,v,(double)y/(double)x});
			edge[v].push_back({v,u,(double)x/(double)y});
		}
		for(int i=1;i<=n;i++) {
			if(BFS(i)==0) {
				printf("Case #%d: No\n",++Ans);
				flag=1;break;
			}
		}
		if(flag==0) printf("Case #%d: Yes\n",++Ans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值