BZOJ 4011: [HNOI2015]落忆枫音 拓扑序Dp

title

BZOJ 4011

LUOGU 3244

简化题意:

给定一张有向无环图,现在要求加入一条边,求加入后以 1 为根的树形图个数。

analysis

首先,不考虑这条加进来的边,这张图是个 \(DAG\)(废话)。

那么根据朱刘算法的推论可知:如果除根节点外每个点都选择一条入边,由于没有环,因此一定会形成一个树形图。

所以
\[ ans=\prod_{i=2}^n In[i],其中In[i]表示第i个点的入度 \]

现在,把这条边加进来,那么我们用上面的公式统计的话,就会有一些不合法的情况,我们需要把他减去。

如果这种情况不合法,那么一定会形成一个环,且环包括这条心加入的边。

所以我们要减去的情况就是:
\[ \sum_{S是原图y\to x的一条路径的点集}\prod_{2\leq j\leq n,j\notin S}In[j] \]

那么我们就可以 \(Dp\) 求解了,

状态:设 \(f_i\) 表示 \(\sum_{S是原图y\to x的一条路径的点集}\prod_{2\leq j\leq n,j\notin S}In[j]\)

状态转移方程:
\[ f_i=\frac{\sum_{j\to i}f[j]}{In[i]} \]

初值:
\[ f_T=\frac{\prod_{i=2}^n In[i]}{In[T]} \]

注意 \(T=1\) 的时候需要特判。

code

#include<bits/stdc++.h>

typedef long long ll;
const int maxm=2e5+10,mod=1e9+7;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x,char str)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++=str;
    }
}

using IO::read;
using IO::write;

int ver[maxm],Next[maxm],head[maxm],len;
inline void add(int x,int y)
{
    ver[++len]=y,Next[len]=head[x],head[x]=len;
}

int In[maxm],b[maxm],n,m,S,T;;
ll f[maxm],inv[maxm], ans=1;
inline void Dp()
{
    f[T]=ans; std::queue<int>q;
    for (int i=1; i<=n; ++i)
        if (!In[i]) q.push(i);
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        f[x]=f[x]*inv[b[x]]%mod;
        for (int i=head[x]; i; i=Next[i])
        {
            int y=ver[i];
            f[y]=(f[y]+f[x])%mod;
            if (!--In[y]) q.push(y);
        }
    }
}

int main()
{
    read(n);read(m);read(S);read(T);
    inv[1]=1;
    for (int i=2; i<=m+1; ++i) inv[i]=(-inv[mod%i]*(ll)(mod/i)%mod+mod)%mod;//逆元

    for (int i=1,x,y; i<=m; ++i) read(x),read(y),add(x,y),++In[y];
    ++In[T];
    for (int i=1; i<=n; ++i) b[i]=In[i];
    for (int i=2; i<=n; ++i) ans=ans*(ll)In[i]%mod;
    --In[T];

    if (T==1) { write(ans,'\n'), IO::flush(); exit(0); }//特判
    Dp();

    ans=(ans-f[S]+mod)%mod;//减去不合法的方案
    write(ans,'\n');
    IO::flush();
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/G-hsm/p/11406842.html