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;
}


你可能感兴趣的:(bzoj4011: [HNOI2015]落忆枫音)