P2606 ZJOI2010 排列计数

[gcd(a,p)=1~~a^{phi(p)} equiv1 mod(p)\ 对于任意bgephi(p),有a^bequiv a^{b~mod~phi(p)+phi(p)}\ b<phi(p),a^bequiv a^{b~mod~phi(n)}(mod~p)\ a和p可以不互质,这里的指数显然满足1\ ]

P2606 ZJOI2010 排列计数

由题意(p_i>plfloor i/2 floor),(p_i>p_{2i}(lle n/2)) (p_i>p_{2i+1}(i<n/2))

显然是个小根堆,在树中填写数字

假设我们在(i)点,考虑剩下(i-1)个点,容易想到(i-1)个节点选(l)个节点作为左子树,剩下(r)个节点做右子树

(f[i]={i-1choose l}*f[l]*f[r])

#include<cstdio>
#define maxn 1000005
#define int long long
using namespace std;
int a[maxn],n,f[maxn],jc[maxn],m;
int qpow(int a,int b){
	int ans = 1;
	while(b){
		if(b & 1) ans = ans * a % m;
		b >>= 1;
		a = a * a % m;
	}
	return ans;
}
int ask(int k,int l){
	if(k == 1||k == 0||k == 2) return 1;
	if(k == 3) return 2;
	int u = a[l<<1],v = a[l<<1|1];
	if(f[u] == 0) f[u] = ask(u,l<<1);
	if(f[v] == 0) f[v] = ask(v,l<<1|1);
	return jc[k-1] * qpow(jc[u],m-2) % m * qpow(jc[k-1-u],m-2) % m * f[u] % m * f[v] % m;
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i = 1;i <= n;i++){
		int k = i;
		while(k) a[k] ++,k>>=1;
	}
	jc[0] = jc[1] = 1;
	for(int i = 2;i<=n;i++)	jc[i] = jc[i-1] * i % m;
	printf("%lld",ask(n,1));
}

更妙的是

答案等价于求一个树的拓扑排序数量

数字的大小关系可以看做是一条有向边,这样以每个位置当点,就可以把整个排列当做一张有向图。而且题目保证有解,所以只一张有向无环图。这样子,我们就可以把排列计数的问题转化为一个图的拓扑排序计数问题

[公式ans=frac{n!}{prod_{i=1}^ns[i]}~~~s[i]为子节点的数量\ f[u]=frac{(s[u]-1)!*prod f[v]!}{prod s[v]!}\ ]

原文地址:https://www.cnblogs.com/shikeyu/p/13365688.html