【bzoj4011】【HNOI2015】落忆枫音【树形DP】

bzoj链接
小视野链接
###题目大意
给出一个DAG,现在新加一条边(给出: x → y x\rightarrow y xy),求以1为根生成的外向树的个数。(题目保证图中从1节点能到达所有节点,可能有自环)
###题解
这种解法是大神zjr想出来的:。
首先算出每个点的入度 d e g i deg_i degi
如果没有环,答案就为 ∏ i = 2 n d e g i \prod_{i=2}^ndeg_i i=2ndegi,因为每个点可以任意选择一条入边。
若加上的边仍没有环或不能选上(连上1或自环),答案也是如此。
然而懒得判有环无环,这两种情况合在一起考虑,这时答案为不加这条边的方案加上不加的方案。不加的方案数显然是 ∏ i = 2 n d e g i \prod_{i=2}^ndeg_i i=2ndegi(deg中不计新加的贡献)。
考虑加上这条边的答案。若无环:先单独把这条边取出来,把树分为以1为根的子树和以 y y y为根的子树,子树中随便取,必须取 x → y x\rightarrow y xy y y y的入边不能取,答案为 ∏ i = 2 , i ̸ = y n d e g i = ∏ i = 2 n d e g i d e g y \prod_{i=2,i\not= y}^ndeg_i=\frac{\prod_{i=2}^ndeg_i}{deg_y} i=2,i̸=yndegi=degyi=2ndegi。若有环,规定只选一条1至 x x x的路径,其他都不选,这样答案为 ∣ S ∣ ∗ ∏ i = 2 , i ̸ = y , i ̸ ∈ S n d e g i = 1 d e g y ∗ ∏ i = 2 n d e g i ∗ ∣ S ∣ ∗ ∏ i ∈ S 1 d e g i |S|*\prod_{i=2,i\not=y,i\not\in S}^ndeg_i=\frac{1}{deg_y}*\prod_{i=2}^ndeg_i*|S|*\prod_{i\in S}\frac{1}{deg_i} Si=2,i̸=y,i̸Sndegi=degy1i=2ndegiSiSdegi1(S为从根节点不经过y到达x的路径点集)。后一部分可以用DP算出来 d p [ u ] = ∑ u → v d p [ v ] d e g [ v ] dp[u]=\sum_{u\rightarrow v}\frac{dp[v]}{deg[v]} dp[u]=uvdeg[v]dp[v],边界为 d p [ i ] = ( i = = X ) dp[i]=(i==X) dp[i]=(i==X)。(注意不能进过y)。
由于在无环的情况下,这个DP值不影响答案,所以一起计算更方便
###代码

//#pragma GCC optimize(3)
#include<iostream>
#include<iomanip>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<ctime>
#define ll long long
#define db double
#define inf 100009
#define infm 100009
#define INF (ll)1e15
#define mod 1000000007LL
#define pi acos(-1)
#define rd(n) {n=0;char ch;int f=0;do{ch=getchar();if(ch=='-'){f=1;}}while(ch<'0'||ch>'9');while('0'<=ch&&ch<='9'){n=(n<<1)+(n<<3)+ch-48;ch=getchar();}if(f)n=-n;}
using namespace std;
int n,m,X,Y;

ll inv[inf];
ll getinv(ll x){
	int tmpx=(ll)x;
/*	if (inv[tmpx]){
		return inv[tmpx];
	}*/
	ll ans=1LL,k=mod-2LL;
	while (k){
		if (k&1LL){
			ans=(ans*x)%mod;
		}
		x=(x*x)%mod;
		k/=2LL;
	}
//	inv[tmpx]=ans;
	return ans;
}

struct edge{
	int y,nxt;
	edge(){}
	edge(int yy,int nn){
		y=yy,nxt=nn;
	}
}e[inf*2];
int head[inf],ecnt;

void addedge(int x,int y){
	ecnt++;
	e[ecnt]=edge(y,head[x]);
	head[x]=ecnt;
	return;
}

ll deg[inf],dp[inf];
bool vis[inf];

void dfs(int u){
	vis[u]=1;
	if (u==X){
		dp[u]=1LL;
		return;
	}
	else if (u==Y){
		return;
	}
	for (int i=head[u];i;i=e[i].nxt){
		int v=e[i].y;
		if (!vis[v]){
			dfs(v);
		}
		dp[u]=(dp[u]+getinv(deg[v])*dp[v]%mod)%mod;
	}
	return;
}

int main(){
	rd(n) rd(m) rd(X) rd(Y)
	int x,y;
	for (int i=1;i<=m;i++){
		rd(x) rd(y)
		addedge(x,y);
		deg[y]++;
	}
	ll ans=1LL;
	for (int i=2;i<=n;i++){
		ans=(ans*deg[i])%mod;
	}
	if (X==Y || Y==1){
		printf("%lld\n",ans);
		return 0;
	}
	dfs(1);
	ans=(ans+ans*dp[1]%mod*getinv(deg[Y])%mod)%mod;
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值