【bzoj4011】落忆枫音
Description
给出一个有n个点和m条边的有向无环图,1号节点入度为0。这个有向无环图存在一个树形子图,是以1号节点为根的包含全部n个点的一棵树。该树形子图可能有多种可能性。
现在向图中加入一条与已有边不同的有向边(连接两个节点但方向不同视为不同的边),这条边可连向自身。原有向无环图添加新边后得到的新图可能会出现环。
求新图中以1为根的树形子图的方案数。(对1000000007取模)
Solution
我们可以得到,有向无环图中树形子图的方案数为2号节点到n号节点的入度的乘积。
设新边为(u,v),新图中2号节点到n号节点的入度的乘积为sum。
若v==1,则答案为sum。
否则令f[i]表示原图中1号节点到i号节点所有路径的价值之和除以该点当前入度(即乘上当前入度模1000000007的逆元),ans=sum-f[u]。
初始值:f[v]=sum。
我们在原图中从1号节点开始进行拓扑排序。
若加入新边后不构成环,由于原图为DAG,则不存在从v节点到u节点的路径,f[u]=0,满足答案为2号节点到n号节点的入度的乘积,即ans=sum;
若加入新边后构成环,f[u]的值即新图中取了整个环的方案数,则ans=sum-f[u]。
Code
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 using namespace std; 6 typedef long long ll; 7 const ll mod=1000000007; 8 struct edge{ 9 int to,next; 10 }e[200010]; 11 int n,m,x,y,head[100010]; 12 int a[100010]={0},in[100010]={0}; 13 ll ans=1,inv[100010]={0,1},f[100010]={0}; 14 ll solve(){ 15 queue<int>q; 16 while(!q.empty()) 17 q.pop(); 18 q.push(1); 19 f[y]=ans; 20 while(!q.empty()){ 21 int u=q.front(); 22 q.pop(); 23 f[u]=f[u]*inv[a[u]]%mod; 24 for(int i=head[u];~i;i=e[i].next){ 25 int v=e[i].to; 26 f[v]=(f[v]+f[u])%mod; 27 in[v]--; 28 if(in[v]==0) 29 q.push(v); 30 } 31 } 32 return (ans-f[x]+mod)%mod; 33 } 34 int main(){ 35 memset(head,-1,sizeof(head)); 36 scanf("%d%d%d%d",&n,&m,&x,&y); 37 for(int i=2;i<=n+1;i++) 38 inv[i]=(mod-mod/i*inv[mod%i]%mod)%mod; 39 a[y]++; 40 for(int i=1;i<=m;i++){ 41 int u,v; 42 scanf("%d%d",&u,&v); 43 e[i]=(edge){v,head[u]}; 44 head[u]=i; 45 in[v]++;a[v]++; 46 } 47 for(int i=2;i<=n;i++) 48 ans=ans*a[i]%mod; 49 printf("%lld\n",y==1?ans:solve()); 50 return 0; 51 }