BZOJ 4011 HNOI2015 落忆枫音+简要题意

Problem

BZOJ

其实前面的背景对题目并没有什么帮助。
给出一张DAG,并再加入一条边x->y,问有多少棵生成树?
保证原DAG上的一号节点入度为0。

Solution

我们用in表示节点的入度,那么对于原DAG上的答案就是 ni=2in[i] ∏ i = 2 n i n [ i ] 。正确性显而易见,只要对于除1外的节点都选择一条入边即是一棵生成树。

在加入边后,可能产生环导致重复计算的答案,那么这个重复计算的数量就是除这个环外所有节点生成树的个数。显然产生的环必定是x->..->y->x的,那么我们就在原DAG考虑从y到x的所有路径的贡献。对于路径P,其贡献为:

i=2,iPnin[i]=ni=2in[i]iPin[i] ∏ i = 2 , i ∉ P n i n [ i ] = ∏ i = 2 n i n [ i ] ∏ i ∈ P i n [ i ]

考虑用DP解决,f[y]初值设为之前算出的答案,作为上方表达式的分子,那么DP时就只需要不断做除法(分母)即可。设对于节点i,P表示y到i的路径,则f[i]表示 jPin[j] ∑ ∏ j ∈ P i n [ j ] 。那么就有 f[v]=u>vf[u]in[v] f [ v ] = ∑ u − > v f [ u ] i n [ v ] 。为了使得能够考虑到所有可能的路径,我们怎么保证这个点的f不会再被更新之后才转移其答案呢?那就需要用拓扑排序了。

Code

#include 
#include 
using namespace std;
typedef long long ll;
const int maxn=100010,mod=1000000007;
struct data{int v,nxt;}edge[maxn<<1];
int n,m,p,x,y,head[maxn],in[maxn],t[maxn],inv[maxn],f[maxn];
ll ans,tmp=1;
queue<int> q;
inline void insert(int u,int v){edge[++p]=(data){v,head[u]};head[u]=p;}
void init()
{
    inv[1]=1;
    for(int i=2;i<=n;i++)
      inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod;
}
void top()
{
    int now;
    f[y]=tmp;
    for(int i=1;i<=n;i++) if(!t[i]) q.push(i);
    while(!q.empty())
    {
        now=q.front();q.pop();
        f[now]=(ll)f[now]*inv[in[now]]%mod;
        for(int i=head[now];i;i=edge[i].nxt)
        {
            t[edge[i].v]--;
            if(!t[edge[i].v]) q.push(edge[i].v);
            f[edge[i].v]+=f[now];
            if(f[edge[i].v]>=mod) f[edge[i].v]-=mod;
        }
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif
    scanf("%d%d%d%d",&n,&m,&x,&y);
    for(int i=1,a,b;i<=m;i++){scanf("%d%d",&a,&b);in[b]++;insert(a,b);}
    init();in[y]++;
    for(int i=2;i<=n;i++) tmp=tmp*in[i]%mod,t[i]=in[i];
    ans=tmp;t[y]--;
    if(y==1){printf("%lld\n",ans);return 0;}
    top();
    printf("%lld\n",(ans-f[x]+mod)%mod);
    return 0;
}

你可能感兴趣的:(=====动态规划=====,HNOI,BZOJ)