1. 程式人生 > >BZOJ 1485 [HNOI2009]有趣的數列

BZOJ 1485 [HNOI2009]有趣的數列

首先將2n個數排列為序列A,從前向後選出n個作為奇數項,剩下的作為偶數項,而且選定的陣列成的有趣的數列只能有一種(選出的奇數項數字和偶數項數字要升序插入數列,排列只有一種)。要保證奇數項小於偶數項,那麼對於每一位A中的數,其前選出的奇數項的個數必然要大於等於偶數項的個數。把它轉化成了經典的進出棧問題。

對於進出棧問題也複習一下:設n個數進出站的總數為h(n),設最後時刻出站的元素為i,那麼比i先進棧且先出棧的種類數為:h(i-1),比i後進棧且先出棧的種數為:h(n-i+1),而i的取值範圍為1~n,於是有h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0)

首先打了個遞推式的寫法,複雜度O(n2)
然後直接用卡特蘭數的組合數公式,分解一下質因數會達到O(nlogn)。

h(n)=Cn2nn+1=Cn2nCn12n

(50分)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>

using namespace std;

int n,mod;
int dp[1000005];

int main()
{
    scanf("%d%d",&n,&mod);
    dp[0
]=dp[1]=1; for(int i=2;i<=n;i++) { for(int j=0;j<=i-1;j++) dp[i]=(dp[i]+1LL*dp[j]%mod*dp[i-j-1]%mod)%mod; } cout<<dp[n]; return 0; }

(100分)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std; const int maxn=2000005; int n,mod,cnt; int prime[maxn],num[maxn],fa[maxn][2]; bool is[maxn]; void linear_shaker() { for(int i=2;i<maxn;i++) { if(!is[i]) { prime[++cnt]=i; fa[i][0]=i; fa[i][1]=1; } int t; for(int j=1;j<=cnt&&(t=prime[j]*i)<maxn;j++) { is[t]=true; fa[t][0]=prime[j]; fa[t][1]=i; if(i%prime[j]==0)break; } } } void update(int x,int val) { while(is[x]) { num[fa[x][0]]+=val; x=fa[x][1]; } num[x]+=val; } int _pow(int x,int y) { int res=1; while(y) { if(y&1)res=(1LL*res*x)%mod; x=(1LL*x*x)%mod; y>>=1; } return res; } int cal() { int res=1; for(int i=2;i<maxn;i++) if(num[i])res=(1LL*res*_pow(i,num[i]))%mod; return res; } int main() { scanf("%d%d",&n,&mod); linear_shaker(); for(int i=n+1;i<=2*n;i++) update(i,1); for(int i=2;i<=n;i++) update(i,-1); update(n+1,-1); printf("%d",cal()); return 0; }