把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4011】[HNOI2015] 落忆枫音(DAG上记忆化搜索)

点此看题面

大致题意: 在一张\(DAG\)(其中\(1\)号点入度为\(0\))上加一条边,求以\(1\)为根的有向生成树个数。

前言

这道题基本上都是自己做出来的,应该还是一道比较简单的题目吧。

考虑不加边

首先,我们考虑不加边的情况。

则显然,对于一个点\(i\),它的父节点有\(deg_i\)种选择方式。

因此,答案为\(\prod_{i=2}^ndeg_i\)

加边需减去的答案

再考虑加上一条边,和不加边的情况相比,若以同样的方式计算答案,这种情况下可能出现的问题就是会出现环。

因此,我们只要减去环的情况数,是不是就可以了呢?

而环的情况数怎么算?

对于一个环,显然环上的点有唯一选择,而环外的点可以任意选择,即:

\[\prod_{i\not∈Circle}deg_i \]

而若我们设\(p=\prod_{i=2}^ndeg_i\),则这就相当于是:

\[\frac p{\prod_{i∈Circle}deg_i} \]

因此,设新增的边为\(s\)\(t\)的一条边,则我们只要从\(t\)\(s\)进行\(dfs\),然后记忆化搜索记下每一个点到\(s\)的答案即可。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define M 200000
#define X 1000000007
#define add(x,y) (e[++ee].nxt=lnk[x],++deg[e[lnk[x]=ee].to=y])
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,s,t,p,f[N+5],ee,lnk[N+5],deg[N+5];struct edge {int to,nxt;}e[M+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
}F;
I int Qpow(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
I int dfs(CI x)//记忆化搜索
{
	if(x==s) return 1LL*p*Qpow(deg[x],X-2)%X;if(~f[x]) return f[x];RI i,res=0;
	for(i=lnk[x];i;i=e[i].nxt) Inc(res,dfs(e[i].to));return f[x]=1LL*res*Qpow(deg[x],X-2)%X;//统计后续答案,并除以当前点度数
}
int main()
{
	RI i,x,y;F.read(n),F.read(m),F.read(s),F.read(t),++deg[t];//注意增加t号点入度
	for(i=1;i<=m;++i) F.read(x),F.read(y),add(x,y);for(p=1,i=2;i<=n;++i) p=1LL*p*deg[i]%X;//统计度数积
	return memset(f,-1,sizeof(f)),printf("%d",t^1?(p-dfs(t)+X)%X:p),0;//特判t=1是无用边
}
posted @ 2020-05-14 07:43  TheLostWeak  阅读(119)  评论(0编辑  收藏  举报