bzoj4011: [HNOI2015]落忆枫音
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4011
思路:首先要脑补一个结论,不考虑新加的边,树的个数=π degree[i](i!=1),degree指入度
因为除了根节点,每个点各选一条入边,就可以组成一棵树。
现在有了这条边x->y,我们如果还用入度乘积统计方案,就有可能多计算一些不合法的方案
这些方案都包含了一个有新边的环,于是我们就要想办法减去这一部分。
我们先统计一个特定的环,不合法的环一定是由一条y->x的路径+这条边x->y构成的,那么我们可以把这个环当一个点,含这个环的不合法方案数就是
Σy->x的不同路径 π (i不在枚举的路径上) degree[i]
于是开始DP,设f[i]表示 Σy->i的不同路径 π (j不在枚举的路径上) degree[j]
那么i的方案数就是所有能一步到i的点j的方案数之和
因为i不在y->j的路径上,所以会多乘一个degree[i],除去即可
转移方程就是f[i]=(Σ(j能一步到i)f[j])/degree[i]
y==1时要特判,不然会除0
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=100010,maxm=400010,mod=1000000007; typedef long long ll; using namespace std; int n,m,sx,sy,pre[maxm],now[maxn],son[maxm],tot,in[maxn],q[maxm+10],head,tail,deg[maxn];ll ans=1,f[maxn],inv[maxm]; void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b,in[b]++,deg[b]++;} ll getinv(int a){ int b=mod-2;ll res=1,j=a; for (;b;b>>=1,j=j*j%mod) if (b&1) res=res*j%mod; return res; } void topDP(){ for (int i=1;i<=n;i++) if (!deg[i]) q[++tail]=i;head=0; f[sy]=ans; while (head!=tail){ if (++head>maxm) head=1; int x=q[head];f[x]=f[x]*inv[deg[x]]%mod; for (int y=now[x];y;y=pre[y]){ f[son[y]]=(f[son[y]]+f[x])%mod; if (!(--in[son[y]])){ if (++tail>maxm) tail=1; q[tail]=son[y]; } } } } int main(){ scanf("%d%d%d%d",&n,&m,&sx,&sy); for (int i=1;i<=m+1;i++) inv[i]=getinv(i); for (int i=1,a,b;i<=m;i++) scanf("%d%d",&a,&b),add(a,b); deg[sy]++; for (int i=2;i<=n;i++) ans=ans*deg[i]%mod; if (sy==1) return printf("%lld\n",ans),0; topDP(),printf("%lld\n",(ans-f[sx]+mod)%mod); return 0; }