[BZOJ4011][HNOI2015]落忆枫音:拓扑排序+容斥原理

分析

又是一个有故事的题目背景。作为玩过原作的人,看题目背景都快看哭了ToT。强烈安利本境系列,话说SP-time的新作要咕到什么时候啊。

好像扯远了嘛不管了。

一句话题意就是求一个DAG再加上一条有向边所构成的有向图的以(1)为根的外向树形图的个数。

考虑一个DAG的情况,答案显然是:

[prod_{i=2}^{n}in[i] ]

其中(in[i])表示结点(i)的入度,这个式子的意思就是给每个非根结点选一条入边。由于是DAG所以这样构造出来的一定是一个外向树形图。

加入一条边后,图上可能会出现环,如果有一些结点选择的入边正好构成一个环的话,那么这样构造出的图是不合法的。

而每个这样的环会让答案减去(frac{prod_{i=2}^{n}in[i]}{prod_{i在环上}in[i]})

设新加入的边为(s o t),考虑到每个环都是由原图中(t)(s)的一条路径加上(s o t)这条新加入的边构成的,所以我们就可以通过拓扑排序统计那个东西了。

时间复杂度可以做到(O(n))。(不过博主因为快速幂算逆元多了个(log)

代码

#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;

inline int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

const int MAXN=100005;
const int MAXM=200005;
const LL MOD=1e9+7;

int n,m,s,t,ecnt,head[MAXN];
int out[MAXN],in[MAXN];
LL f[MAXN];
std::queue<int> q;

struct Edge{
	int to,nxt;
}e[MAXM];

inline void add_edge(int bg,int ed){
	++ecnt;
	e[ecnt].to=ed;
	e[ecnt].nxt=head[bg];
	head[bg]=ecnt;
}

inline LL qpow(LL x,LL y){
	LL ret=1,tt=x%MOD;
	while(y){
		if(y&1) ret=ret*tt%MOD;
		tt=tt*tt%MOD;
		y>>=1;
	}
	return ret;
}

LL topo(){
	while(!q.empty()) q.pop();
	rin(i,1,n)
		if(!out[i])
			q.push(i);
	f[s]=1;
	while(!q.empty()){
		int x=q.front();q.pop();
		f[x]=f[x]*qpow(in[x],MOD-2)%MOD;
		trav(i,x){
			int ver=e[i].to;
			--out[ver];
			if(!out[ver]) q.push(ver);
			f[ver]=(f[ver]+f[x])%MOD;
		}
		if(x==t) return f[x];
	}
}

int main(){
	n=read(),m=read(),s=read(),t=read();
	++in[t];
	rin(i,1,m){
		int u=read(),v=read();
		++out[u],++in[v];
		add_edge(v,u);
	}
	LL ans=1;
	rin(i,2,n) ans=ans*in[i]%MOD;
	if(t==1){
		printf("%lld
",ans);
		return 0;
	}
	ans=(ans-topo()*ans%MOD+MOD)%MOD;
	printf("%lld
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/ErkkiErkko/p/10406425.html