posts - 21,comments - 0,views - 1051

 

来源

P7113 [NOIP2020] 排水系统 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 

题意

一个有向无环图有n个结点,其中有m个入口分别都接收1吨污水。

污水进入每个结点后,会均等地从当前结点的每一个排出管道流向其他排水结点,而最终排水口将把污水排出系统。

没有排出管道的结点便可视为一个最终排水口。

求:在该城市的排水系统中,每个最终排水口会排出多少污水。

 

输入

5 1【n m】【接下来 n 行,第 i 行用于描述结点 i 的所有排出管道。
3 2 3 5【结点1有3个排出管道,分别是2 3 5】
2 4 5【结点2有2个排出管道,分别是4 5】
2 5 4【结点3有2个排出管道,分别是5 4】
0【结点4为一个最终排水口】
0【结点5为一个最终排水口】

 

输出要求

按照编号从小到大的顺序,给出每个最终排水口排出的污水体积。其中体积使用分数形式进行输出,即每行输出两个用单个空格分隔的整数 p,q,表示排出的污水体积为 p / q 。

要求 p与 q 互素,q = 1 时也需要输出 q。

 

输出

1 3
2 3

 


 

注释版代码

 

复制代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#define ll long long
using namespace std;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,m,rushuiguanshu[100001],final[100001],over[100001];
/*
1. rushuiguanshu[i]:存储i结点有几个入水管。
对应至拓扑排序的定义则是:在整个工程中,作为当前工程的先决条件的工程是否已经全部完成。如果全部完成,则rushuiguanshu[i]=0;否则rushuiguanshu[i]大于0,代表i之前尚未完成的工程数。
2. final[i]:标记最终排出口。若结点i是图的最终排出口,则final[i]=1。
3. over[i]:
如果i入水管都被遍历过了,即从别处进入i的水量都得到了累加,则over[i]标记为1。即等效于rushuiguanshu[i]=0。(其实可以删掉这个)
对应至拓扑排序的定义则是:在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始。如果over[i]=1,则表示可以开始在i的拓扑排序后续的工程,即以i为先决条件的工程已经具备了i这个条件。
ps:rushuiguanshu[i]=0等价于 over[i]=1, over[i]=0等价于 rushuiguanshu[i]>0
*/

ll fenzi[100001],fenmu[100001];
ll gcd(ll x,ll y){
    if(y==0)
        return x;
    return gcd(y,x%y);
}
void add(int i,ll x,ll y){
    if(y==0)
        return;
    if(fenmu[i]==0){
        fenzi[i]=x;
        fenmu[i]=y;
        return;
    }
    ll p1=fenzi[i]*y+fenmu[i]*x; 
    ll p2=fenmu[i]*y;//通分分母 
    ll p3=gcd(p1,p2);//找到最大公约数 
    fenzi[i]=p1/p3;//分子除以最大公约数。得到约分后的分子 
    fenmu[i]=p2/p3;//分母除以最大公约数。得到约分后的分母
    return;
}

vector<int> a[500001];
queue<int> q;
void tp(){
    for(int i=1;i<=n;i++)
    {
        if(!rushuiguanshu[i]){//找到没有入水管的结点,即图的总入口
            over[i]=1;//没有入水口就是没有先决条件,则可以开始完成i的后续工程,则标记为1 
            q.push(i);//从污水总入口开始拓扑,所以将总入口入队 
            fenzi[i]=1,fenmu[i]=1;//在数组中初始化每个总入口的水流量,即水流量为1吨
        }
    }
    
     /*
     父节点入队后,以bfs序将水汇入子结点,即等价于拓扑排序所定义的对于子节点标记当前父节点的这
     一先决条件已完成的功能 。所有父节点都被标记,即已经没有需要入水的父节点,则子节点的先决条
     件都已完成,可以开始以该子节点为父节点的后续子孙节点的工程,而首先就是将该子节点入队,作
     为父节点,进行对下一轮子节点的标记 
     */
    while(!q.empty()){//这里不是循环,而是出队。每一次出队以p为前驱结点,进行下一层的bfs
        int p=q.front();
        q.pop();
        if(final[p])//最终排出口不会对某结点的流入造成影响,因为最终排出口没有出度。所以任何结点都不会有来自最终排出口的入度,所以也不用进行后续的分流等工作。 
            continue;
        for(int i=0;i<a[p].size();i++){//对于结点p进行bfs,即为p的每个后继节点加入分流 
            int cur=a[p][i];//cur就是从队列中取出当前分流将要汇入的结点a[p][i]的意思 
            add(cur,fenzi[p],fenmu[p]*(1ll*a[p].size()));
            //add(后继节点的下标,前驱节点的分子,前驱节点的分母*size)
            //因为有n个出水管且平均分配,所以分流的流量就是分流之前流量的1/x,所以乘1/x即可,所以就分母乘n就可以 
            //add(cur, fenzi[], fenmu[]*x)的作用:将前驱结点的流量分流后加入到后继结点中去 
            if(over[cur])//如果当前节点已经没有其他入水口/没有其他未完成的先决条件了,则不用继续等待入队,跳过 
                continue;
            rushuiguanshu[cur]--;//灭掉一个入水口 
            if(rushuiguanshu[cur]==0){//注意!!第一次if到结点5的时候,5还有2和3这两个入度,所以这里还不能进入if,也就是说,这时候的5并不会被入队! 
            //那么通过结点1入队的有哪些结点呢?对了,有2和3这两个结点!因为他们都是入度=0的点啦!
                over[cur]=1;// cur没有入水口了,标记一下 
                q.push(cur);//结合前面if(over[cur]) 可以看出,入队的结点都是已经完成所有先决条件工作的结点,入队后则可以继续为下一层待完成的结点累计先决条件了。 
            }
        }
    }
    return;
}

int main()
{
    //freopen("water.in","r",stdin);
    //freopen("water.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++){
        int d=read();//第i个结点有d个排出口 
        if(d==0){
            final[i]=1;//没有排出口的是最终排出口,标记为1 
            continue;//最终排出口没有次级排出口,不需要读入数据 
        }
        while(d--){
            int v;//结点对应的每个排出口依次是v 
            v=read();
            a[i].push_back(v);//对于结点i,将v加入i的队列表当中,以实现bfs 
            rushuiguanshu[v]++;
        }
    }
    tp();
    for(int i=1;i<=n;i++){
        if(final[i]){//只输出最终排出口 
            add(i,0,1);//分数的加法和约分 
            printf("%lld %lld\n",fenzi[i],fenmu[i]);
        }
    }
    return 0;
}
View Code
复制代码

 

 

无注释版代码

 

复制代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#define ll long long
using namespace std;
inline ll read(){
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,m,in[100001],out[100001],book[100001];
ll fenzi[100001],fenmu[100001];
ll gcd(ll x,ll y){
    if(y==0)
        return x;
    return gcd(y,x%y);
}
void add(int i,ll x,ll y){
    if(y==0)
        return;
    if(fenmu[i]==0){
        fenzi[i]=x;
        fenmu[i]=y;
        return;
    }
    ll p1=fenzi[i]*y+fenmu[i]*x; 
    ll p2=fenmu[i]*y;//通分分母 
    ll p3=gcd(p1,p2);//找到最大公约数 
    fenzi[i]=p1/p3;//分子除以最大公约数。得到约分后的分子 
    fenmu[i]=p2/p3;//分母除以最大公约数。得到约分后的分母
    return;
}

vector<int> a[500001];
queue<int> q;
void tp(){
    for(int i=1;i<=n;i++)
    {
        if(!in[i]){
            book[i]=1;
            q.push(i);
            fenzi[i]=1,fenmu[i]=1;
        }
    }

    while(!q.empty()){
        int p=q.front();
        q.pop();
        if(out[p])
            continue;
        for(int i=0;i<a[p].size();i++){
            int cur=a[p][i];
            add(cur,fenzi[p],fenmu[p]*(1ll*a[p].size()));
            if(book[cur])
                continue;
            in[cur]--;
            if(in[cur]==0){
                book[cur]=1;
                q.push(cur);
            }
        }
    }
    return;
}

int main()
{
    //freopen("water.in","r",stdin);
    //freopen("water.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++){
        int d=read();
        if(d==0){
            out[i]=1;
            continue;
        }
        while(d--){
            int v;
            v=read();
            a[i].push_back(v);
            in[v]++;
        }
    }
    tp();
    for(int i=1;i<=n;i++){
        if(out[i]){
            add(i,0,1);
            printf("%lld %lld\n",fenzi[i],fenmu[i]);
        }
    }
    return 0;
}
View Code
复制代码

 

 

代码填空 

 

 

 


 

图解

 

题目里的tricks

1. 题目并没有给出入水口,要通过rushuiguanshu[]找出来,然后入队开始bfs。

从图中也能看出,没有入水管道的只有1号结点。

2. 在拓扑的基础之上引入了流量的概念,但是流量顺应了拓扑的结构,并且依赖于拓扑来进行计算

3. 拓扑在bfs之上的变体:通过rushuiguanshu[]不为0,控制了bfs的走向。入水管数不为0,则:

不能入队,即不能从该结点开始bfs。即满足了如下定义:在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的。

在这里,由于后续结点的分流来自于当前结点,故当前结点的总流入量没有计算完的话,是无法计算后面结点的流入量的。

而结点可以进行bfs,则等价于当前结点的总流入量已经全部计算完成。

例如:

作为图的内部结点,rushuiguanshu[]一开始不为0,直到rushuiguanshu[]为0后入队,over[]也标记为1,表明入水管都被遍历过了

4. over数组其实完全可以被rushuiguanshu[]代替...

 

get姿势

1. 二维队列数组

定义:vector<int> a[500001];

使用:for(int i=0;i<a[p].size();i++)

2. 拓扑排序是变体的bfs。实现其控制的核心在于,存储每个结点的入度的数组--,直到0才可以入队bfs。而这个操作对应了拓扑排序的定义:在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始。

 

最后丢一张稍复杂的图给你品~

原图:

图片来源:

https://www.jianshu.com/p/b1dd76f666da

 

拓扑时的边和点:

 

posted on   Josee不是john  阅读(264)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示