Uva 11174 - Stand in a Line(思维+逆元)

All the people in the byteland want tostand in a line in such a way that no person stands closer to the front of theline than his father. You are given the information about the people of thebyteland. You have to determine the number of ways the bytelandian people canstand in a line.

 

Input

First line of the input contains T (T <14) the number of test case. Then following lines contains T Test cases. Eachtest case starts with 2 integers n (1 ≤ n ≤ 40000) and m (0 ≤ m < n). n isthe number of people in the byteland and m is the number of people whose fatheris alive. These n people are numbered 1 . . . n. Next m line contains twointegers a and b denoting that b is the father of a. Each person can have at mostone father. And no person will be an ancestor of himself.

 

Output

For each test case the output contains asingle line denoting the number of different ways the soldier can stand in asingle line. The result may be too big. So always output the remainder ondividing ther the result by 1000000007.

 

Sample Input

3

3 2

2 1

3 1

3 0

3 1

2 1

 

Sample Output

3

 

【题意】

   有n个村民,有多少种方式可以把他们排成一列,使得没有人排在他父亲的前面(有些人的父亲不存在)?输入n和村民之间的关系,输出方案数模1e9+7的结果。

 

【思路】

   n个村民的关系构成了一个森林,可以创建一个祖先结点,这个结点表示那些原本父亲不存在的村民的父亲。这样整个村民间关系就构成了一个树。设f(i)是以i为根结点的排列方案数,s(i)是以i为根结点的所有结点数量(包括自身),那么f(i)=f(c1)f(c2)…f(ck)*{[s(i)-1]!/[s(c1)!s(c2)!...s(ck)!]},其中,c1,c2…ck是i的k个子结点。后半部分{}中的式子表示的是一个可重排列,即把s(i)个村民看成相同元素,保证分别属于s(c1),s(c2)…,s(ck)的结点只有一种排列方式的情况数,由于分别有f(c1),f(c2),…f(ck)种排列方式,所以还要把它们再乘上。

   上面只是得到了一个递推式,再写出一项递推式f(c1)=f(x1)f(x2)…f(xm)*{[s(c1)-1]!/[s(x1)!s(x2)!...s(xm)!]}将其代回到第一个式子里,就能发现分子上有一个[s(c1)-1]!,而分母上有一个s(c1)!约分后得到1/s(c1),如果我们假设的祖先结点是root那么答案就是f(root),将所有项展开后就能得到ans=f(root)=[s(root)-1]!/{s(1)!s(2)!...s(n)!},而[s(root)-1]!=n!,由dfs可以求出s[]的值,再根据费马小定理用快速幂求出乘法逆元即可计算结果。

 

#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;

const int mod = 1e9 + 7;
const int maxn = 40050;

int n, m, a, b;
int s[maxn];//s[i]表示树中以i为根结点包含的所有结点数量
bool used[maxn];
vector<int> G[maxn];

ll pow(ll x, int n) {
	ll ans = 1;
	while (n > 0) {
		if (n & 1) ans = (ans * x) % mod;
		x = (x * x) % mod;
		n >>= 1;
	}
	return ans;
}

int dfs(int v) {//返回结点v为根所包含的结点数量
	if (used[v]) return s[v];
	used[v] = 1;

	int ans = 1;//包括自身
	for (int i = 0; i < G[v].size(); i++) {
		ans += dfs(G[v][i]);
	}
	return s[v] = ans;
}

int main() {
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d", &n, &m);
		memset(s, 0, sizeof(s));
		memset(used, 0, sizeof(used));
		for (int i = 0; i < maxn; i++) {
			G[i].clear();
		}
		for (int i = 0; i < m; i++) {
			scanf("%d%d", &a, &b);
			G[b].push_back(a);
		}
		for (int i = 1; i <= n; i++) {
			if (!used[i]) {
				dfs(i);
			}
		}

		ll ans = 1, totals = 1;
		for (ll i = 2; i <= n; i++) {
			ans = (ans * i) % mod;
		}
		for (int i = 1; i <= n; i++) {
			totals = (totals * s[i]) % mod;
		}
		ans = (ans * pow(totals, mod - 2)) % mod;

		printf("%lld\n", ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值