UVA 1436 - Counting heaps(计数问题)

UVA 1436 - Counting heaps

题目链接

题意:给定一个树的结构,放1-n数字进去,父亲结点值必须小于子节点,问情况有几种.

思路:f[u]表示以u为子树的情况,那么子树情况为f(v1), f(v2), f(v3)... f(vn).去组成子树相当于从中选s(v1), s(v2), s(v3) ... s(vn).根据组合数学,情况为f(v1) f(v2) ... f(vn) (s(u) - 1)! \ (s(v1)! s(v2)! ... * s(vn)!)化简后得到公式:

f(u) = (s(root) - 1)! / s(1)s(2)s(3)...s(n)

由于答案很大要模上m,但是m不是素数,不能直接求逆,只能把分子分母分别分解质因子去消,最后得到都是分子形式在去取模。

代码:

#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;

const int N = 500005;
int t, n, cnt[N], prime[N], vis[N], pn = 0, f[N], ispri[N];
long long m;

int bfs() {//用dfs必然RE
	queue<int> Q;
	for (int i = 1; i <= n; i++)
		if (!vis[i]) Q.push(i);
 	while (!Q.empty()) {
 		int now = Q.front();
 		Q.pop();
 		if (now == 0) break;
 		cnt[f[now]] += cnt[now];
 		vis[f[now]]--;
 		if (vis[f[now]] == 0)
 			Q.push(f[now]);
  	}
}

void solve(int num, int v) {
	for (int i = 0; i < pn && prime[i] <= num; i++) {
		while (num % prime[i] == 0) {
			cnt[prime[i]] += v;
			num /= prime[i];
  		}
  		if (ispri[num]) {//不加此剪枝必然TLE
  			cnt[num] += v;
     		        break;
    	        }
 	}
}

long long pow_mod(long long x, int k) {
	long long ans = 1;
	while (k) {
		if (k&1) ans = ans * x % m;
		x = x * x % m;
		k >>= 1;
 	}
	return ans;
}

int main() {
	for (int i = 2; i < N; i++) {
		if (vis[i]) continue;
		ispri[i] = 1;
		prime[pn++] = i;
		for (int j = i; j < N; j += i)
			vis[j] = 1;
 	}
	scanf("%d", &t);
	while (t--) {
		scanf("%d%lld", &n, &m);
		for (int i = 1; i <= n; i++)
			cnt[i] = 1;
		f[1] = 0;
		memset(vis, 0, sizeof(vis));
		for (int i = 2; i <= n; i++) {
			scanf("%d", &f[i]);
			vis[f[i]]++;
  		}
  		bfs();
    	memset(vis, 0, sizeof(vis));
  		for (int i = 1; i <= n; i++)
  			vis[cnt[i]]++;
		memset(cnt, 0, sizeof(cnt));
		for (int i = 2; i <= n; i++) {
			solve(i, 1);
			if (vis[i])
   				solve(i, -vis[i]);
		}
		long long ans = 1;
		for (int i = 0; i < pn; i++) {
			if (cnt[prime[i]] == 0) continue;
  			ans = (ans * pow_mod((long long)prime[i], cnt[prime[i]])) % m;
		}
  		printf("%lld\n", ans);
 	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值