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