• E. Count Seconds(DAG/拓扑排序/树形dp)


    题目
    参考

    题意

    给定一个DAG,其中出度为0的结点只有一个。每个结点有一个初始值a[i]。每秒,每个结点会发生以下事情:

    • 对于当前结点u,他的值a[u]会减一。
    • 对于所有与v相连的结点,他的值a[v]会加一。

    问什么时刻,所有结点的a值都为0。

    顶点n和边数m
    1<=n,m<=1000
    答案对998244353取模。

    思路

    我们把a值用小水珠代替描述,更形象点。
    我们关注汇点,即出度为0的点。所有的结点,最终都会通过这个汇点。
    关注任意一个结点u,以及它的初始值a[u],结点从u走到汇点,最多需要n步,因为总共就n个结点。那么经过n步后,所有一开始从u出发的“水珠”,一定和连上了汇点。这些“水珠”流完,当前仅当最后一个水珠从汇点消失。

    因此。
    我们现模拟走n步,如果在n步范围内,所有结点的a值都为0,则直接得到答案。
    如果在n步后,还存在非0的a值,这时可以计算每个结点u,他会对汇点贡献多少次。
    在这里插入图片描述
    例如,对于上图,汇点是5。那么

    • 从汇点本身走到汇点,路径只有1条,贡献次数是1
    • 从2,4走到5的路径只有1条,因此它们的贡献次数是1.
    • 从3走到5,路径有2条,因此它的贡献次数是2
    • 从1走到5,路径有3条,因此它的贡献次数是3.

    如何求每个结点的贡献次数呢?用拓扑排序即可,反向建图,从汇点开始,计算汇点到每个结点的路径条数。

    计算出贡献次数后,每个结点的贡献即为 a[i]*贡献次数。

    此外注意取模运算。

    详见代码

    代码

    #include
    using namespace std;
    #define ll long long
    const int maxn = 1010;
    const int mod = 998244353;
    
    int n, m, x, y;
    ll a[maxn];
    vector<int> g[maxn], rg[maxn];
    ll d[maxn], tmp[maxn], sum[maxn];
    queue<int> q;
    void step() {// 模拟走1步 
        for (int i = 1; i <= n; ++i) {
            tmp[i] = a[i];
            a[i] = 0;
        }
        for (int i = 1; i <= n; ++i) {
            if (!tmp[i]) {
                continue;
            }
            a[i] += tmp[i] - 1;
            for (auto v: g[i]) {
                ++a[v];
            }
        }
        for (int i = 1; i <= n; ++i) {
            if (a[i] >= mod) {// 这里+mod,是因为前面有 tmp[i] - 1,防止运算为负数 
                a[i] = a[i] % mod + mod;
            } else {
                a[i] = a[i] % mod;
            }
        }
    }
    int cal() {// 暴力 模拟走n轮 
        for (int run = 1; run <= n; ++run) {
            bool zero = true;
            for (int i = 1; i <= n; ++i) {
                if (a[i]) {
                    zero = false;
                    break;
                }
            }
            if (zero) {// 所有结点都走完了 
                return run - 1;
            }
            step();
        }
        return n;
    }
    void solve() {
    	scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i) {
            scanf("%lld", &a[i]);
            g[i].clear();
            rg[i].clear();
            d[i] = 0;
            sum[i] = 0;
        }
    	for (int i = 1; i <= m; ++i) {
    	    scanf("%d%d", &x, &y);
    	    g[x].push_back(y);// 建图 
    	    rg[y].push_back(x);// 反向建图 
    	    ++d[x];// 结点的出度,对应反向图中 结点的入度 
    	}
    	int res = cal();
    	if (res < n) {
    	    printf("%d\n", res);
    	    return;
    	}
    
    	// topu sort
        while (!q.empty()) {
            q.pop();
        }
        // 找出汇点 
    	for (int i = 1; i <= n; ++i) {
    	    if (!d[i]) {
    	        sum[i] = 1;
    	        q.push(i);
    //	        break;
    	    }
    	} 
        while (!q.empty()) {// 拓扑排序,计算每个结点的贡献次数 
            int u = q.front();
            q.pop();
            sum[u] %= mod;// 注意取模 
            for (auto v: rg[u]) {
                sum[v] += sum[u];
                if (--d[v] == 0) {
                    q.push(v);
                }
            }
        }
        ll ans = n;// 计算ans 注意取模 
        for (int i = 1; i <= n; ++i) {
            ll tmp = sum[i] * a[i] % mod;
            ans = (ans + tmp) % mod;
        }
        printf("%lld\n", ans);
    }
    int main() {
    	int t;
    	scanf("%d", &t);
    	while (t--) {
    		solve();
    	}
    	return 0;
    }
    
    • 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
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    最后

    weixin gongzhonghao搜 对方正在debug,关注下,一起快乐刷题吧~

  • 相关阅读:
    11-golang流程控制
    Rust基本数据类型-字符串
    loop_list单向循环列表
    修炼k8s+flink+hdfs+dlink(四:k8s(一)概念)
    华泰证券:达达集团(DADA)3Q23业绩前瞻:短期业绩承压
    【C++】面向对象的理解
    黑号照妖镜API接口 淘宝旺旺信誉
    Java计算机毕业设计大学生校园社团管理系统源码+系统+数据库+lw文档
    【Java-----IO流(四)之转换流详解】
    酷睿处理器型号前面的字母代表什么
  • 原文地址:https://blog.csdn.net/weixin_43918473/article/details/126131039