[BZOJ2111][ZJOI2010]排列計數-題解

VictoryCzt發表於2019-01-04

題目地址-洛谷

題目地址-BZOJ


簡易題意

讓你求1n1\sim n所有的排列中滿足Pi>Pi2P_i>P_{\lfloor\frac{i}{2}\rfloor}的個數。

n106,Mod109n\leqslant 10^6,Mod\leqslant 10^9且為質數


其實,我們可以發現,對於每個PiP_i,它必然要有兩個比它小的在i2\lfloor\frac{i}{2}\rfloori+12\lfloor\frac{i+1}{2}\rfloor的位置,這個性質就十分像一個大根堆,可以顯然的看出P1P_1絕對最大。

所以我們把這個排列放在一個大根堆上來看,其實也就是一棵完全二叉樹,每個點的值滿足比它的兩個兒子大,那麼我們考慮當前點uu,左兒子為lul_u,右兒子為rur_uszeusze_u為子樹大小,那麼這個子樹的方案數就相當於,我們相當於在1szeu11\sim sze_u-1中選一些數出來,放在左右兒子的子樹內,那麼方案數顯然就是Cszeu1szeluC_{sze_u-1}^{sze_{l_u}},就是在這裡選出szelusze_{l_u}個數,然後再乘以在左兒子和右兒子中的方案數就是答案了,所以我們預處理階乘直接遞迴計算即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e6+1;
int n;ll Mod;
ll fac[N],inv_fac[N],sze[N];
ll fpow(ll a,ll b){
	ll res=1;
	for(;b;b>>=1,a=(a*a)%Mod)
		if(b&1)res=(res*a)%Mod;
	return res;
}
ll C(ll n,ll m){
	if(m>n) return 0;
	if(m==0||n==m) return 1;
	if(n<Mod)return fac[n]*inv_fac[m]%Mod*inv_fac[n-m]%Mod;
	return C(n/Mod,m/Mod)*C(n%Mod,m%Mod)%Mod;
}
ll calc(int a){
	if(a>n) return 1;
	return calc(a<<1)*calc(a<<1|1)%Mod*C(sze[a]-1,sze[a<<1])%Mod;//這裡a<<1可能爆空間,所以開兩倍 
}
int main(){
	scanf("%d%lld",&n,&Mod);
	fac[0]=fac[1]=inv_fac[0]=sze[1]=1;
	for(ll i=2;i<=n;i++)
		fac[i]=(fac[i-1]*i)%Mod,sze[i]=1;
	for(int i=n;i>1;i--)sze[i>>1]+=sze[i];
	inv_fac[n]=fpow(fac[n],Mod-2);
	for(ll i=n-1;i>=1;i--)
		inv_fac[i]=(inv_fac[i+1]*(i+1))%Mod;
	printf("%lld\n",calc(1));
	return 0;	
}

一種更神奇的方法是,原問題相當於求一個二叉樹的拓撲排序數量,直接套公式可得:

ans=n!i=1nszei ans=\frac{n!}{\prod_{i=1}^n sze_i}

還是同樣處理一下子樹大小和逆元階乘即可計算。

原理我也不懂QWQ

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=2e6+1;
ll n,p,s[M],inv[M];
int main()
{
    scanf("%lld%lld",&n,&p);
    inv[1]=inv[0]=1;
    for(ll i=1;i<=n;i++) s[i]=1;
    for(ll i=2;i<=n;i++) inv[i]=(p-p/i)*inv[p%i]%p;//線性求逆元
    for(ll i=n;i>1;i--) s[i>>1]+=s[i];
    ll ans=1;
    for(ll i=1;i<=n;i++)   ans=ans*i%p*inv[s[i]]%p;
    printf("%lld",ans);
    return 0;
}

相關文章