P2606 ZJOI2010 排列计数

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

P2606 ZJOI2010 排列计数

由题意\(p_i>p\lfloor i/2\rfloor\),\(p_i>p_{2i}(l\le n/2)\) \(p_i>p_{2i+1}(i<n/2)\)

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

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

\(f[i]={i-1\choose 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]!}\\ \]

posted @ 2020-07-23 12:51  INFP  阅读(101)  评论(0编辑  收藏  举报