bzoj 4011: [HNOI2015]落忆枫音 拓扑序dp+容斥原理

本文探讨了在有向无环图中增加一条边后,从特定节点出发生成树的数量计算问题。通过分析图的性质,引入动态规划方法解决新增边带来的多余计数问题,并给出了具体的实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

给出一个有向无环图(节点1必然可以到达所有点)和任意一条边(可以是自环),问以1为根的生成树有多少种。有向图的生成树是指每个节点与父节点的连边必然是由父节点连向它。
n<=100000,m<=200000

分析

想到了最后一步,但是没想到这个东西可以用dp来求。。。

我们发现一个DAG的生成树数量必然是除了1以外所有点的度数乘积。证明的话随便yy一下就好了。
现在多了一条边,我们考虑用同样的方法来求。但是发现会多算一些情况。这些情况必然都是选了新加入的那条边并且这条边在一个环中。那么就要考虑容斥掉这些情况对答案的贡献。

这些情况对答案的贡献为SyxΠni=2iSdegree[i]

而这个是可以用dp来求的。

f[i]表示SyiΠni=2iSdegree[i]

显然有f[i]=j>if[j]degree[i]

初值为f[y]=Πni=2i!=ydegree[i]

注意y=1要特判掉。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;

typedef long long LL;

const int N=100005;
const int MOD=1000000007;

int n,m,s,t,inv[N*2],tmp[N],tot,last[N],cnt,d[N],f[N],a[N];
struct edge{int to,next;}e[N*2];
queue<int> que;

int read()
{
    int 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;
}

void addedge(int u,int v)
{
    e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
}

void toposort()
{
    for (int i=1;i<=n;i++) if (!d[i]) que.push(i);
    while (!que.empty())
    {
        int u=que.front();que.pop();
        a[++tot]=u;
        for (int i=last[u];i;i=e[i].next)
        {
            tmp[e[i].to]--;
            if (!tmp[e[i].to]) que.push(e[i].to);
        }
    }
}

void get_inv()
{
    inv[1]=1;
    for (int i=2;i<=m+1;i++) inv[i]=(LL)(MOD-MOD/i)*inv[MOD%i]%MOD;
}

int main()
{
    n=read();m=read();s=read();t=read();
    for (int i=1;i<=m;i++)
    {
        int x=read(),y=read();
        addedge(x,y);
        d[y]++;tmp[y]++;
    }
    toposort();
    get_inv();
    d[t]++;
    int ans=1;
    for (int i=2;i<=n;i++)  ans=(LL)ans*d[i]%MOD;
    if (t==1)
    {
        printf("%d",ans);
        return 0;
    }
    f[t]=(LL)ans*inv[d[t]]%MOD;
    for (int i=1;i<=tot;i++)
    {
        int x=a[i];
        if (!f[x]) continue;
        for (int j=last[x];j;j=e[j].next) f[e[j].to]=(f[e[j].to]+(LL)f[x]*inv[d[e[j].to]]%MOD)%MOD;
    }
    printf("%d",(ans-f[s]+MOD)%MOD);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值