• 【bzoj4011】[HNOI2015]落忆枫音 容斥原理+拓扑排序+dp


    题目描述

    给你一张 $n$ 个点 $m$ 条边的DAG,$1$ 号节点没有入边。再向这个DAG中加入边 $x o y$ ,求形成的新图中以 $1$ 为根的外向树形图数目模 $10^9+7$ 。

    输入

    输入文件的第一行包含四个整数 n、m、x 和 y ,依次代表枫叶上的穴位数、脉络数,以及要添加的脉络是从穴位 x 连向穴位 y 的。 

    接下来 m 行,每行两个整数,由空格隔开,代表一条脉络。第 i 行的两个整数为 ui 和 vi ,代表第 i 条脉络是从穴位 ui 连向穴位 vi 的。 

    输出

    输出一行,为添加了从穴位 x连向穴位 y的脉络后,枫叶上以穴位 1 为根的脉络树的方案数对 1,000,000,007取模得到的结果。 

    样例输入

    4 4 4 3
    1 2
    1 3
    2 4
    3 2

    样例输出

    3


    题解

    容斥原理+拓扑排序+dp

    直接处理外向树形图的数目比较困难,考虑容斥,用 每个点选一条入边的方案数 减去 每个点选一条入边形成不了外向树形图的方案数 得到答案。

    每个点选一条入边的方案数直接计算即可。

    对于形成不了外向树形图的,由于给出的图是一个DAG加上一条边,因此选出非外向树形图的一定是选择了 $x o y$ 边加上 $y-to x$ 的路径,形成一个环。

    显然我们再从 $y$ 到 $x$ 跑拓扑排序+dp即可。相当于路径上点 $i$ 的价值为 $frac i{d[i]}$ ,统计所有路径的价值之和,再乘上每个节点的度数之积为第二部分的答案。

    时间复杂度为求逆元的 $O(nlog n)$

    注意一下1号节点的处理,我的处理方法为:虚拟一个0号点连向1号点,以0为跟求外向树形图即可。

    #include <queue>
    #include <cstdio>
    #define N 100010
    #define mod 1000000007
    using namespace std;
    typedef long long ll;
    queue<int> q;
    int head[N] , to[N << 1] , next[N << 1] , cnt , vis[N] , ind[N];
    ll d[N] , f[N];
    inline void add(int x , int y)
    {
        to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
    }
    inline ll pow(ll x , int y)
    {
        ll ans = 1;
        while(y)
        {
            if(y & 1) ans = ans * x % mod;
            x = x * x % mod , y >>= 1;
        }
        return ans;
    }
    void dfs(int x)
    {
        int i;
        for(i = head[x] ; i ; i = next[i])
            if(!vis[to[i]])
                vis[to[i]] = 1 , dfs(to[i]);
    }
    int main()
    {
        d[1] = 1;
        int n , m , s , t , i , x , y;
        ll ans = 1;
        scanf("%d%d%d%d" , &n , &m , &s , &t) , d[t] ++ ;
        for(i = 1 ; i <= m ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , d[y] ++ ;
        for(i = 1 ; i <= n ; i ++ ) ans = ans * d[i] % mod , d[i] = pow(d[i] , mod - 2);
        vis[t] = 1 , dfs(t);
        for(x = 1 ; x <= n ; x ++ )
            for(i = head[x] ; i ; i = next[i])
                if(vis[x] && vis[to[i]])
                    ind[to[i]] ++ ;
        f[t] = d[t] , q.push(t);
        while(!q.empty())
        {
            x = q.front() , q.pop();
            for(i = head[x] ; i ; i = next[i])
            {
                if(vis[to[i]])
                {
                    f[to[i]] = (f[to[i]] + f[x] * d[to[i]]) % mod , ind[to[i]] -- ;
                    if(!ind[to[i]]) q.push(to[i]);
                }
            }
        }
        printf("%lld
    " , ans * (1 - f[s] + mod) % mod);
        return 0;
    }
    

     

  • 相关阅读:
    c++ stl中的二分查找
    2015年---移动端webapp知识总结
    移动端网站优化指南-WAP篇
    ASO优化总结(基于网络分享的知识总结归纳)
    验证数字的正则表达式集
    个人的浏览器重置样式表(总结)
    微信或移动端网页的meta
    移动端字体和字体大小规范
    min-device-pixel-ratio
    Emmet语法实例(帮助快速开发)
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/8305692.html
Copyright © 2020-2023  润新知