bzoj 4011: [HNOI2015]落忆枫音

         题意: 给你一个DAG,再给你加一条边,之后图不保证是DAG了,求新图中以一为根的生成树有多少个。

        由于原图是个DAG,我们对每个点随机找爹,总能形成一棵生成树,那么答案就是2到n所有点入度的乘积。在加了一条边 x->y 之后,如果再套用这个公式,无非就是多了加了这条边之后与一条y->x的路径构成环的方案。

        那么我们可以对于每一条y->x的路径考虑,它多贡献的答案就是除了它以外其他点的入度之积,可以看作又分成了若干个DAG。

        这样的话思路就挺明显了,令dp[i]表示从y出发走到i点对答案多产生的贡献,令ans为2号点到n号点的乘积,初始dp[y]设置为ans/deg[y],按照图的拓扑序进行转移,走到每一个新点就相当于把这个点加入从y出发的路径,即

                                                                  dp[nex]+=dp[now]/deg[nex]

         这样求得的dp[x]即为答案。

         下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 200005
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int n,m,nx,ny,tot,ans=1;
int head[maxn],nex[maxn],to[maxn];
void add(int x,int y)
{
	to[++tot]=y;nex[tot]=head[x];head[x]=tot;
}
int deg[maxn],deg1[maxn],inv[maxn];
void getinv()
{
	inv[1]=1;
	for(int i=2;i<=m+1;i++)
	{
		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	}
}
int q[maxn],dp[maxn];
void topologicalsort()
{
	int h=0,t=-1;
	dp[ny]=ans;
//	cout<<"its "<<ny<<" "<<ans<<endl;
	for(int i=1;i<=n;i++)
		if(deg[i]==0)
			q[++t]=i;
	while(h<=t)
	{
		int now=q[h]; h++;
//		cout<<now<<" "<<dp[now]<<endl;
		dp[now]=(1ll*dp[now]*inv[deg1[now]])%mod;
//		cout<<now<<" "<<dp[now]<<" "<<inv[deg1[now]]<<endl;
		for(int i=head[now];i;i=nex[i])
		{
			dp[to[i]]=(dp[to[i]]+dp[now])%mod;
			deg[to[i]]--;
			if(deg[to[i]]==0)
				q[++t]=to[i];
		}
	}
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&nx,&ny); deg1[ny]++;
	for(int i=1;i<=m;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		deg[y]++;
		deg1[y]++;
	}
//	int ans=1;
	for(int i=2;i<=n;i++)
		ans=(1ll*ans*deg1[i])%mod;
	if(ny==1)
	{
		printf("%d\n",ans);
		return 0;
	}
//	cout<<ans<<endl;
	getinv();
//	cout<<ans<<endl;
	topologicalsort();
	printf("%d\n",(ans-dp[nx]+mod)%mod);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值