美文网首页
NOIP2020T1排水系统详细思路+题解

NOIP2020T1排水系统详细思路+题解

作者: 乔治yuanbo | 来源:发表于2020-12-06 01:17 被阅读0次

    T1肯定是简单题,其它题没把握的情况下,花2小时也值得。此题不难,1小时应该能拿90分。
    题目见洛谷7113
    先看题,题目中也写了,没有环路,那么这就是个典型的有向无环图,从初始的接收口开始,依次向排出管道放水,一层一层放,某个中间节点先把来自爸爸、爷爷的水都接收完,再向下一级排出管道放水,很容易想到拓扑排序,按拓扑序依次给每个节点的儿子们放水即可。
    观看输出要求,水量得保存到一个结构体里面

    1、先把拓扑排序写对

    #include <cstdio>
    #define MAXN 10//此处为了调试方便,先开小一点,够样例2即可
    using namespace std;
    struct e{
        int to, nxt;
    } es[MAXN*5+1];//存图,按题意,开节点个数5倍的数组即可
    //下面cur和head用来链式前向星存图;rd保存节点入度,入度为0就是接水口;cd保存节点出度,出度为0就是题目要求输出的排出口;q是队列,用于拓扑排序
    int cur, head[MAXN+1], rd[MAXN+1], cd[MAXN+1], q[MAXN+1];
    int n, m;
    //链式前向星存图加边函数
    void addedge(int node, int to){
        es[++cur].to = to;
        es[cur].nxt = head[node];
        head[node] = cur;
    }
    //下面拓扑排序
    void tsort(){
        int qi = 0, qj = 0;//队头队尾指针,队头指针指向实际元素,队尾指针指向队尾元素的下一个位置
        for(int i = 1; i <= n; i++){
            if (rd[i] == 0){
                q[qj++] = i;
            }
        }
        while(qi != qj){
            int node = q[qi++];
            for(int i = head[node]; i; i = es[i].nxt){
                rd[es[i].to]--;
                if (rd[es[i].to] == 0){
                    q[qj++] = es[i].to;
                }
            }
        }
    }
    int main(){
        scanf("%d%d", &n, &m);
        int d, a;
        for(int i = 1; i <= n; i++){
            scanf("%d", &d);
            cd[i] = d;
            while(d--){
                scanf("%d", &a);
                addedge(i, a);
                rd[a]++;
            }
        }
        tsort();
        //拓扑排序以后,队列q里面的就是排好序的节点,序号[0, n)
        for(int i = 0; i < n; i++){
            printf("%d ", q[i]);
        }
        return 0;
    }
    

    上面代码存图,拓扑排序,有样例1输出拓扑序看一下写对没有。没问题就继续撸。

    2、分水

    存水量得用一个结构体,分别存放分子分母。然后重载+和/两个运算符,加用来加水,/用来平分水,发给儿子们。先别着急约分,输出结构手动约分和样例对比,没问题再加约分。

    struct w{
        int p, q;
        w operator +(const w &r){
            w ret;
            if (q == 0){
                ret.q = r.q;
                ret.p = r.p;
            } else if (r.q == 0){
                ret.p = p;
                ret.q = q;
            } else {
                ret.q = q * r.q;
                ret.p = (p * r.q + q * r.p);
            }
            return ret;
        }
        w operator /(int x){
            w ret;
            ret.p = p;
            ret.q = q * x;
            return ret;
        }
    } ws[MAXN+1];//顺便开数组,每个节点都有个元素存水量
    

    加好以后,在原来拓扑排序找接水口的时候,给接水口赋值初始水量:

    for(int i = 1; i <= n; i++){
            if (rd[i] == 0){
                q[qj++] = i;
                ws[i].p = ws[i].q = 1;//分子分母都是1
            }
        }
    

    然后到拓扑排序的下面添加分水代码,从前到后依次分就行了

        //拓扑排序以后,队列q里面的就是排好序的节点,序号[0, n)
        //从前到后依次分水
        for(int i = 0; i < n; i++){
            int node = q[i];
            if(ws[node].p > 0){//有水
                w wc = ws[node] / cd[node];
                for(int j = head[node]; j; j = es[j].nxt){
                    int to = es[j].to;
                    ws[to] = ws[to] + wc;
                }
            }
        }
    

    最后加上输出:

    for(int i = 1; i <= n; i++){
            if (cd[i] == 0){
                printf("%d %d\n", ws[i].p, ws[i].q);
            }
        }
    

    运行样例看一下,手动约分,没问题

    3、约分

    约分很好办,先求最大公约数gcd,然后都除以gcd。如何求最大公约数?2020年csp-s提高组初赛里就有,此处注意,2020csp提高组初赛有如何求最大公约数,2020csp提高组复赛有拓扑排序的题,所以考完csp以后,必须把真题全部吃透,在考noip的时候很有优势。

    int gcd(int a, int b){
        if (b == 0) return a;
        return gcd(b, a % b);
    }
    struct w{
        int p, q;
        w operator +(const w &r){
            w ret;
            if (q == 0){
                ret.q = r.q;
                ret.p = r.p;
            } else if (r.q == 0){
                ret.p = p;
                ret.q = q;
            } else {
                ret.q = q * r.q;
                ret.p = (p * r.q + q * r.p);
            }
            if (p != 0){//约分
                int g = gcd(ret.p, ret.q);
                ret.p /= g;
                ret.q /= g;
            }
            return ret;
        }
        w operator /(int x){
            w ret;
            ret.p = p;
            ret.q = q * x;
            if (p != 0){
                int g = gcd(ret.p, ret.q);
                ret.p /= g;
                ret.q /= g;
            }
            return ret;
        }
    } ws[MAXN+1];
    

    加上约分,样例1和2都通过了,感觉很好,先把MAXN放大到最大的1e5,再运行样例3,发现大多数是对的,但有的不对,不用说肯定是爆int了,需要放大到unsigned long long,如果还爆就需要高精度了,考场来不及,就算了。

    3+、官方数据放出后更新,先把分母乘起来,再约分就晚了

    会爆unsign long long,90分变成60分,所以必须尽早约分,先算出分母的最小公倍数,使得中间结果尽量小,才不会爆ull。

    ull gcd(ull a, ull b){
        if (b == 0) return a;
        return gcd(b, a % b);
    }
    
    ull lcm(ull a, ull b){
        ull g = gcd(a, b);
        return (max(a, b) / g) * min(a, b);
    }
    struct w{
        ull p, q;
        w operator +(const w &r){
            w ret;
            if (q == 0){
                ret.q = r.q;
                ret.p = r.p;
            } else if (r.q == 0){
                ret.p = p;
                ret.q = q;
            } else {
                if (q % r.q == 0){
                    ret.p = p + r.p * (q / r.q);
                    ret.q = q;
                } else if (r.q % q == 0){
                    ret.p = p * (r.q / q) + r.p;
                    ret.q = r.q;
                } else {
                    ull lm = lcm(q, r.q);
                    ret.q = lm;
                    ret.p = p * (lm / q) + r.p * (lm / r.q);
                }
            }
            if (p != 0){
                ull g = gcd(ret.p, ret.q);
                ret.p /= g;
                ret.q /= g;
            }
            return ret;
        }
        w operator /(int x){
            w ret;
            if (p % x == 0){
                ret.p = p / x;
                ret.q = q;
                return ret;
            }
            ret.p = p;
            ret.q = q * x;
            if (p != 0){
                ull g = gcd(ret.p, ret.q);
                ret.p /= g;
                ret.q /= g;
            }
            return ret;
        }
    } ws[MAXN+1];
    

    4、最终代码

    #include <cstdio>
    #include <algorithm>
    #define MAXN 100000
    typedef unsigned long long ull;
    using namespace std;
    ull gcd(ull a, ull b){
        if (b == 0) return a;
        return gcd(b, a % b);
    }
    
    ull lcm(ull a, ull b){
        ull g = gcd(a, b);
        return (max(a, b) / g) * min(a, b);
    }
    struct w{
        ull p, q;
        w operator +(const w &r){
            w ret;
            if (q == 0){
                ret.q = r.q;
                ret.p = r.p;
            } else if (r.q == 0){
                ret.p = p;
                ret.q = q;
            } else {
                if (q % r.q == 0){
                    ret.p = p + r.p * (q / r.q);
                    ret.q = q;
                } else if (r.q % q == 0){
                    ret.p = p * (r.q / q) + r.p;
                    ret.q = r.q;
                } else {
                    ull lm = lcm(q, r.q);
                    ret.q = lm;
                    ret.p = p * (lm / q) + r.p * (lm / r.q);
                }
            }
            if (p != 0){
                ull g = gcd(ret.p, ret.q);
                ret.p /= g;
                ret.q /= g;
            }
            return ret;
        }
        w operator /(int x){
            w ret;
            if (p % x == 0){
                ret.p = p / x;
                ret.q = q;
                return ret;
            }
            ret.p = p;
            ret.q = q * x;
            if (p != 0){
                ull g = gcd(ret.p, ret.q);
                ret.p /= g;
                ret.q /= g;
            }
            return ret;
        }
    } ws[MAXN+1];
    
    struct e{
        int to, nxt;
    } es[MAXN*5+1];//存图,按题意,开节点个数5倍的数组即可
    //下面cur和head用来链式前向星存图;rd保存节点入度,入度为0就是接水口;cd保存节点出度,出度为0就是题目要求输出的排出口;q是队列,用于拓扑排序
    int cur, head[MAXN+1], rd[MAXN+1], cd[MAXN+1], q[MAXN+1];
    int n, m;
    void addedge(int node, int to){
        es[++cur].to = to;
        es[cur].nxt = head[node];
        head[node] = cur;
    }
    //下面拓扑排序
    void tsort(){
        int qi = 0, qj = 0;
        for(int i = 1; i <= n; i++){
            if (rd[i] == 0){
                q[qj++] = i;
                ws[i].p = ws[i].q = 1;
            }
        }
        while(qi != qj){
            int node = q[qi++];
            for(int i = head[node]; i; i = es[i].nxt){
                rd[es[i].to]--;
                if (rd[es[i].to] == 0){
                    q[qj++] = es[i].to;
                }
            }
        }
    }
    int main(){
        scanf("%d%d", &n, &m);
        int d, a;
        for(int i = 1; i <= n; i++){
            scanf("%d", &d);
            cd[i] = d;
            while(d--){
                scanf("%d", &a);
                addedge(i, a);
                rd[a]++;
            }
        }
        tsort();
        //拓扑排序以后,队列q里面的就是排好序的节点,序号[0, n)
        //从前到后依次分水
        for(int i = 0; i < n; i++){
            int node = q[i];
            if(ws[node].p > 0 && cd[node] > 0){//有水
                w wc = ws[node] / cd[node];
                for(int j = head[node]; j; j = es[j].nxt){
                    int to = es[j].to;
                    ws[to] = ws[to] + wc;
                }
            }
        }
        for(int i = 1; i <= n; i++){
            if (cd[i] == 0){
                printf("%llu %llu\n", ws[i].p, ws[i].q);
            }
        }
    
        return 0;
    }
    

    5、深搜

    写完拓扑排序才发现,直接深搜也可以,更简单......

    #include <cstdio>
    #define MAXN 100000
    typedef __int128_t ull;
    using namespace std;
    ull gcd(ull a, ull b){
        return b == 0 ? a : gcd(b, a % b);
    }
    struct w{
        ull p, q;
        w operator +(const w &r){
            w ret;
            if (q == 0){
                ret.q = r.q;
                ret.p = r.p;
            } else if (r.q == 0){
                ret.p = p;
                ret.q = q;
            } else {
                ret.q = q * r.q;
                ret.p = (p * r.q + q * r.p);
            }
            if (p != 0){
                ull g = gcd(ret.p, ret.q);
                ret.p /= g;
                ret.q /= g;
            }
            return ret;
        }
        w operator /(int x){
            w ret;
            if (p % x == 0){
                ret.p = p / x;
                ret.q = q;
                return ret;
            }
            ret.p = p;
            ret.q = q * x;
            if (p != 0){
                ull g = gcd(ret.p, ret.q);
                ret.p /= g;
                ret.q /= g;
            }
            return ret;
        }
    } ws[MAXN+1];
    
    struct e{
        int to, nxt;
    } es[MAXN*5+1];//存图,按题意,开节点个数5倍的数组即可
    //下面cur和head用来链式前向星存图;rd保存节点入度,入度为0就是接水口, cd保存节点出度。
    int cur, head[MAXN+1], rd[MAXN+1], cd[MAXN+1];
    int n, m;
    void addedge(int node, int to){
        es[++cur].to = to;
        es[cur].nxt = head[node];
        head[node] = cur;
    }
    //深搜,有向没有环,和树差不多,不用考虑太多,x表示要搜的节点,add表示加的水量
    void dfs(int x, w add){
        if(head[x] == 0){
            ws[x] = ws[x] + add;
            return;
        }
    
        w wc = add / cd[x];
        for(int j = head[x]; j; j = es[j].nxt){
            dfs(es[j].to, wc);
        }
    }
    
    void p128(ull x){
        if(x > 9)
            p128(x / 10);
        printf("%d", x % 10);
    }
    
    int main(){
        // freopen("water.in", "r", stdin);
        // freopen("water.out", "w", stdout);
        scanf("%d%d", &n, &m);
        int d, a;
        for(int i = 1; i <= n; i++){
            scanf("%d", &d);
            cd[i] = d;
            while(d--){
                scanf("%d", &a);
                addedge(i, a);
                rd[a]++;
            }
        }
    
        w wc;
        wc.p = wc.q = 1;
        for(int i = 1; i <= m; i++){
            dfs(i, wc);
        }
        for(int i = 1; i <= n; i++){
            if (head[i] == 0){
                p128(ws[i].p);
                printf(" ");
                p128(ws[i].q);
                printf("\n");
            }
        }
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:NOIP2020T1排水系统详细思路+题解

          本文链接:https://www.haomeiwen.com/subject/mrgnwktx.html