[HNOI2009]有趣的数列 卡特兰数
题解:
观察到题目其实就是要求从长为2n的序列中选n个放在集合a,剩下的放在集合b,使得集合a和集合b中可以一一对应的使a中的元素小于b。
2种想法(实质上是一样的)。
1,相当于前1位中至少要选1个放入a,前3位中至少要选2位放入a,前5位中至少要选3位放入a......前2n - 1位中恰好选n位放入a。
2,用0表示放入a集合,1表示放入b集合。则每个1都必须有一个左边的0与之匹配,相当于对于任意位置前面0的个数大于等于1的个数。
不管是哪种,其实都可以看做括号匹配问题。。。。。。所以就是卡特兰数了
因为n比较大,且p不一定为质数,因此可以用一个数组存下每个质因子有几个,对于乘法就累加,除法就减掉,然后把最后剩下的累乘起来就是答案。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 2001000 5 #define maxn 2000000 6 #define LL long long 7 8 int tot, n, p; 9 int pri[AC], last[AC]; 10 LL have[AC], ans; 11 bool ispri[AC]; 12 13 void get() 14 { 15 last[1] = 1; 16 for(R i = 2; i <= maxn; i ++) 17 { 18 if(!ispri[i]) pri[++ tot] = i, last[i] = i; 19 for(R j = 1; j <= tot; j ++) 20 { 21 int now = pri[j]; 22 if(i * now > maxn) break; 23 ispri[i * now] = true, last[i * now] = now; 24 if(!(i % now)) break; 25 } 26 } 27 } 28 29 void pre(){ 30 scanf("%d%d", &n, &p); 31 } 32 33 inline void cal(int x){//分解质因数 34 while(last[x] != 1) ++ have[last[x]], x /= last[x]; 35 } 36 37 inline void cut(int x){ 38 while(last[x] != 1) -- have[last[x]], x /= last[x]; 39 } 40 41 inline LL qpow(LL x, int have) 42 { 43 LL ans = 1; 44 while(have) 45 { 46 if(have & 1) ans = ans * x % p; 47 x = x * x % p, have >>= 1; 48 } 49 return ans; 50 } 51 52 void work() 53 { 54 int b = 2 * n; 55 for(R i = 1; i <= b; i ++) cal(i); 56 for(R i = 1; i <= n; i ++) cut(i), cut(i); 57 cut(n + 1), ans = 1; 58 for(R i = 1; i <= b; i ++) 59 if(have[i]) ans = ans * qpow(i, have[i]) % p; 60 printf("%lld\n", ans); 61 } 62 63 int main() 64 { 65 // freopen("in.in", "r", stdin); 66 get(); 67 pre(); 68 work(); 69 // fclose(stdin); 70 return 0; 71 }