【题解】[NOIP2020] 排水系统 题解

一、题目是什么

化简题意:一个 \(n\) 的点组成的有向图,从 \(1\) ~ \(m\) 个点里面倒 \(1\) 吨水,等分向每一点流去,求每个最终排水口(出度为 \(0\))有多少水。

看数据,很明显地,要高精度或者分数之比来表示水量,分数之表示不知道能不能用 long long,我用的 __int128

我们先不考虑高精度来解决这个问题。

看入水口,题目有说“并且这些结点没有汇集管道”,说明 \(1\) ~ \(m\) 入度为 \(0\).

“管道不会形成回路”,即保证无环,则不用考虑自己流出去的会返回来。

这不就...

拓扑排序...

没了...

不知道拓扑排序的,,

好的我这就讲。

二、拓扑排序

什么是拓扑排序?

首先排除排序(

拓扑排序,就是,把一张图啪的一下,搞成一个线性序列,使得图中任意一对顶点 \(u\)\(v\),若边 \(<u,v>\in E(G)\),则 \(u\) 在线性序列中出现在 \(v\) 之前。

比如说,有一张学习路线图,在学习一个课程之前要有几个前置课程,现在求出我要怎么学习才能保证学完所有的课程。

最后的结果叫做拓扑序列,这个过程就是拓扑排序。

按照这个拓扑序列学习,能保证在学习到第 \(i\) 个课程时,其前置课程都学过了。

很明显,必须保证无环,不然就会出现,我要学一个课程,但是学这个课程之前需要我要先学完这个课程(禁止套娃

拓扑排序的方法:

  • \(\texttt{Step1.}\) 建两个队列 \(q,ans\).
  • \(\texttt{Step2.}\) 找到所有入度为 \(0\) 的点,放入 \(q\).
  • \(\texttt{Step3.}\) 取出 \(q\) 的头 \(top\)\(ans\) 放入 \(top\).
  • \(\texttt{Step4.}\) 删掉 \(top\),所有与 \(top\) 相连的点的入度减 \(1\),如果入度为 \(0\) 则放入 \(q\).
  • \(\texttt{Step5.}\) 返回 \(\texttt{Step3}\),直到 \(q\) 为空。
  • \(\texttt{Step6.}\) 如果 \(ans\) 的元素个数等于 \(n\) 说明有拓扑序列。

其中 \(ans\) 即为所求

为什么是这样?

每一次 \(q\) 里面放的都是当前情况入度为 \(0\) 的点然后扩展,之后放入 \(ans\).

那为什么?

再拿课程举例子。

我们先学的一定是要没有任何前置课程,或者前置课程已经学过的课程。

这样才能保证能学会这门课。

而第 \(i\) 个点的入度,就相当于,对于第 \(i\) 门课程,你还有多少前置课程没学完。

三、再看题目

RT,是不是是很显然的拓扑排序?

现在比较难的就是水量的表示了,我们用分数表示比较简单,题目的输出也是分数形式。

就直接建一个结构体,就直接就上去搞。

这里直接放代码:

inline void bwr(__int128 x) {
    if(x < 0) { putchar('-'); x = -x; }
    if(x > 9) bwr(x / 10); putchar(x % 10 + '0');
}

__int128 Gcd_(__int128 a,__int128 b){
    if(a % b == 0)return b;
    return Gcd_(b,a % b);
}

__int128 Lcm_(const __int128 &a,const __int128 &b){ return a * b / Gcd_(a,b); }

struct Frac{
    __int128 up,down;
    Frac(){}
    Frac(__int128 up_,__int128 down_) : up(up_) , down(down_) {}
}flow_ans[N];

Frac operator + (const Frac &x,const Frac &y){
    __int128 tlcm__ = Lcm_(x.down,y.down);
    return Frac(tlcm__ / x.down * x.up + tlcm__ / y.down * y.up,tlcm__);
}

Frac operator * (const Frac &x,const Frac &y){
    return Frac(x.up * y.up,x.down * y.down);
}

Frac ts_F(const Frac &x){
    __int128 tgcd__ = Gcd_(x.up,x.down);
    return Frac(x.up / tgcd__,x.down / tgcd__);
}

其中 bwr 函数是用作输出 __int128 类型的数的。

ts_F 函数用来化简分数。

然后?

没有然后了,就这样。

最后理一下思路:

  • 拓扑排序求出排水系统拓扑序列
  • \(1\) ~ \(m\) 的位置放 \(1\) 吨水
  • 按照拓扑序列进行放水
  • 输出出度为 \(0\) 的点的答案(分数形式)

这里放一个完整代码:

/*Copyright (C) 2013-2021 LZE*/
#include<bits/stdc++.h>
#define fo(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
#define INF 0x7fffffff
#define mod 1000000007
#define eps 1e-6

using namespace std;
typedef unsigned long long ull;
typedef long long ll;

const int N = 1000010;
const int M = 1000010;
ll n,m;
struct GraphEdge{
    ll to,next;
}edge[M];
ll head[N] = {0},num = 0;

struct GraphNode{
    ll outdegree;
    ll indegree;
}node[N];

void add(ll u,ll v){
    edge[++num].to = v;
    edge[num].next = head[u];
    head[u] = num;
    node[u].outdegree++;
    node[v].indegree++;
}

bool vis[N] = {0};
ll node_cnt = 0;

ll tp_s[N] = {0};
ll top = 0;

void solve(){
    queue<ll> q;
    for(ll i = 1;i <= n;i++){
        if(node[i].indegree == 0)
            q.push(i);
    }
    while(!q.empty()){
        ll p = q.front(); q.pop();
        vis[p] = true;
        tp_s[++top] = p;
        for(ll i = head[p];i;i = edge[i].next){
            ll to = edge[i].to;
            if(!vis[to]){
                node[to].indegree--;
                if(node[to].indegree == 0){
                    q.push(to);
                }
            }
        }
    }
}

inline void bwr(__int128 x) {
    if(x < 0) { putchar('-'); x = -x; }
    if(x > 9) bwr(x / 10); putchar(x % 10 + '0');
}

__int128 Gcd_(__int128 a,__int128 b){
    if(a % b == 0)return b;
    return Gcd_(b,a % b);
}

__int128 Lcm_(const __int128 &a,const __int128 &b){ return a * b / Gcd_(a,b); }

struct Frac{
    __int128 up,down;
    Frac(){}
    Frac(__int128 up_,__int128 down_) : up(up_) , down(down_) {}
}flow_ans[N];

Frac operator + (const Frac &x,const Frac &y){
    __int128 tlcm__ = Lcm_(x.down,y.down);
    return Frac(tlcm__ / x.down * x.up + tlcm__ / y.down * y.up,tlcm__);
}

Frac operator * (const Frac &x,const Frac &y){
    return Frac(x.up * y.up,x.down * y.down);
}

Frac ts_F(const Frac &x){
    __int128 tgcd__ = Gcd_(x.up,x.down);
    return Frac(x.up / tgcd__,x.down / tgcd__);
}

int main() {
    scanf("%lld%lld",&n,&m);
    ll u,d;
    for(ll i = 1;i <= n;i++){
        if(i <= m)flow_ans[i] = Frac(1,1);
        else flow_ans[i] = Frac(0,1);
        scanf("%lld",&d);
        for(ll j = 1;j <= d;j++){
            scanf("%lld",&u);
            add(i,u);
        }
    }
    solve();
    for(ll i = 1;i <= n;i++){
        if(node[tp_s[i]].outdegree == 0){
            continue;
        }
        // printf("%lld ",tp_s[i]);
        for(ll j = head[tp_s[i]];j;j = edge[j].next){
            ll to = edge[j].to;
            // printf("%lld ",to);
            flow_ans[to] = flow_ans[tp_s[i]] * Frac(1,node[tp_s[i]].outdegree) + flow_ans[to];
            flow_ans[to] = ts_F(flow_ans[to]);
        }
        // printf("\n");
    }
    for(ll i = 1;i <= n;i++){
        if(node[i].outdegree == 0){
            bwr(flow_ans[i].up); cout << " "; bwr(flow_ans[i].down);
            cout << endl;
        }
    }
    return 0;
}

posted @ 2021-09-19 19:26  lzexmpoane  阅读(452)  评论(0)    收藏  举报