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]!}\\
\]