NOIP2020 排水系统

该篇文章讲述了如何使用拓扑排序的方法解决一个关于有向无环图的排水系统问题,其中污水从入水口均匀流入,目标是找出每个出水口流出污水的最简分数。通过限制中间排水点数量和出度,设计算法处理大数运算以求得最终结果。
摘要由CSDN通过智能技术生成

P7113 [NOIP2020] 排水系统

题目大意

有一个排水系统,是有向无环图,编号为 1 1 1 m m m的点为入水口,这些点都没有入度。没有出度的点为出水口。

现在,每个入水口都接收一吨的污水。污水进入每个点后,会均匀地从这个点的有向边流向其他点。求每个出水口会出多少污水,用最简分数表示,输出分子和分母。

1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 10 1\leq n\leq 10^5,1\leq m\leq 10 1n105,1m10

数据保证每个点的出度不超过 5 5 5,污水从入水口到出水口的过程中不会经过超过 10 10 10个中间排水点。


题解

首先,很显然要用拓扑。

因为污水从入水口到出水口的过程中不会经过超过 10 10 10个中间排水点,而且保证每个点的出度不超过 5 5 5,所以最终的分母大小不会超过 6 0 11 60^{11} 6011 1 , 2 , 3 , 4 , 5 1,2,3,4,5 1,2,3,4,5的最小公倍数是 60 60 60)。这个已经超过 long long \text{long long} long long的范围了,但不超过 1 0 20 10^{20} 1020,所以我们可以用多个变量来存储一个数。

因为最终的分母一定是 6 0 11 60^{11} 6011的因数,所以我们可以将每个分数的分母都设为 6 0 11 60^{11} 6011,那么一开始每个入水口的污水量都是 6 0 11 6 0 11 \dfrac{60^{11}}{60^{11}} 60116011。每次转移时,分子一定能分成对应的若干份,将这个点的分子加到这个点的有向边指向的点即可。因为分母一定,所以只需要存储分子,在输出的时候让分子和 6 0 11 60^{11} 6011约分即可。因为 6 0 11 60^{11} 6011的质因子只有 2 , 3 , 5 2,3,5 2,3,5,所以只需要判断分子和分母是否能同时被 2 , 3 , 5 2,3,5 2,3,5整除,这样的话,不用 gcd ⁡ \gcd gcd也能约分。

code

#include<bits/stdc++.h>
using namespace std;
const long long bl=1e9;
int n,m,ct[100005];
vector<int>pt,v[100005];
queue<int>q;
struct big{
	long long v1,v2,v3;
	friend big operator+(big x,big y){
		big re;
		re.v1=(x.v1+y.v1)%bl;
		re.v2=(x.v2+y.v2+(x.v1+y.v1)/bl)%bl;
		re.v3=x.v3+y.v3+(x.v2+y.v2+(x.v1+y.v1)/bl)/bl;
		return re;
	}
	friend big operator/(big x,int y){
		big re;
		re.v3=x.v3/y;
		re.v2=(x.v3%y*bl+x.v2)/y;
		re.v1=((x.v3%y*bl+x.v2)%y*bl+x.v1)/y;
		return re;
	}
}c,d1,d2,w[100005];
bool pd2(big x){
	if(x.v1%2==0) return 1;
	return 0;
}
bool pd3(big x){
	if((x.v1+x.v2+x.v3)%3==0) return 1;
	return 0;
}
bool pd5(big x){
	if(x.v1%5==0) return 1;
	return 0;
}
void print(big x){
	long long wr=x.v3*bl+x.v2;
	if(wr==0) printf("%lld",x.v1);
	else{
		printf("%lld",wr);
		int p[15];
		for(int i=1;i<=9;i++){
			p[i]=x.v1%10;x.v1/=10;
		}
		for(int i=9;i>=1;i--) printf("%d",p[i]);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,k;i<=n;i++){
		scanf("%d",&k);
		if(k==0) pt.push_back(i);
		for(int j=1,x;j<=k;j++){
			scanf("%d",&x);
			v[i].push_back(x);
			++ct[x];
		}
	}
	c=(big){0,279705600,36};
	for(int i=1;i<=m;i++) w[i]=c;
	for(int i=m+1;i<=n;i++) w[i]=(big){0,0,0};
	for(int i=1;i<=m;i++){
		q.push(i);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=0;i<v[u].size();i++){
			w[v[u][i]]=w[v[u][i]]+w[u]/v[u].size();
			--ct[v[u][i]];
			if(!ct[v[u][i]]) q.push(v[u][i]);
		}
	}
	for(int i=0;i<pt.size();i++){
		d1=w[pt[i]];d2=c;
		while(pd2(d1)&&pd2(d2)){
			d1=d1/2;d2=d2/2;
		}
		while(pd3(d1)&&pd3(d2)){
			d1=d1/3;d2=d2/3;
		}
		while(pd5(d1)&&pd5(d2)){
			d1=d1/5;d2=d2/5;
		}
		print(d1);
		printf(" ");
		print(d2);
		printf("\n");
	}
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值