zoukankan      html  css  js  c++  java
  • 关于Catalan数

    仍然是数学

    卡特兰数是一个非常神奇的东西

    序列长这样↓

    1, 1, 2, 5, 14, 42, 132, 429, 1430, 4862, 16796, 58786……(从第零项开始)

    通常比较常用的应该是递推式和组合数的求法

    递推式:

    f(n)=f(n-1)*(4n-2)/(n+1)

    其中令f(0)=1

    组合数:

    f(n)=C(2n,n)/(n+1)

    通常情况下,碰到一道题,你不确定是不是卡特兰数,可以手动或写个暴力跑出来比对结果,一般来说前七八项符合条件,那就没什么问题了,反正我碰到的就是这个样子2333

    我碰到过的卡特兰数题好像不是很多。吧

    【一】二叉树

    求n个节点构成的不同二叉树个数(n≤8000)

    这是一道很经典的题目,很多地方都有,n的范围可以到七八千左右吧

    最朴素的算法就是暴力搜索了,虽然拿不了几分==

    把前面几个算出来,可以发现就是卡特兰数的序列,然后就可以直接写了

    发现是卡特兰数不难,但是关键选择计算卡特兰数的方法

    首先n那么大,显然是要高精度的,高精度的话复杂度就和答案长度有关了,n为七八千的时候,答案长度会达到四五千,是非常大的一个数,所以必须要考虑压位

    递推式的话,时间复杂度为O(nlen)也就是递推复杂度乘上高精度乘除的复杂度

    下面放上fp代码↓

     const tt=1000000000;
     type arr=array[0..550]of int64;
     var n:longint;
         ans,ans1:arr;
     procedure init;
      var i:longint;
       begin
        assign(input,'secret.in');reset(input);
        assign(output,'secret.out');rewrite(output);
        readln(n);
       end;
     function mul(a:arr;x:longint):arr;
      var i:longint;
          c:arr;
       begin
        fillchar(c,sizeof(c),0);
        c[0]:=a[0];
        for i:=1 to c[0] do
         begin
          inc(c[i],a[i]*x);
          inc(c[i+1],c[i] div tt);
          c[i]:=c[i] mod tt;
         end;
        if c[c[0]+1]>0 then inc(c[0]);
        while (c[0]>1)and(c[c[0]]=0) do dec(c[0]);
        exit(c);
       end;
     function divx(a:arr;x:longint):arr;
      var i:longint;
          c:arr;
          yu:int64;
      begin
       fillchar(c,sizeof(c),0);
       c[0]:=a[0];yu:=0;
       for i:=c[0] downto 1 do
        begin
         yu:=a[i] mod x;
         inc(a[i-1],yu*tt);
         c[i]:=a[i] div x;
        end;
       if c[c[0]+1]>0 then inc(c[0]);
       while (c[c[0]]=0)and(c[0]>1) do dec(c[0]);
       exit(c);
      end;
     procedure main;
      var i:longint;
       begin
        ans[0]:=1;ans[1]:=1;
        for i:=2 to n do
          ans:=divx(mul(ans,4*i-2),i+1);
       end;
     procedure print;
      var i,j:longint;
          s:string;
      begin
       write(ans[ans[0]]);
       for i:=ans[0]-1 downto 1 do
        begin
         str(ans[i],s);
         for j:=9 downto length(s)+1 do write(0);
         write(s);
        end;
       close(input);close(output);
      end;
     begin
      init;
      main;
      print;
     end.

    如果选组合数的话,按照组合数定义一个个跑的话复杂度和递推差不多==

    所以可以进行拆分质因子,因为任何一个合数都可以拆分成若干个质因子相乘的形式,质数看成它本身即↓

    X=(p1^a1)*(p2^a2)*(p3^a3)*(p4^a4)*……*(pk^ak)  (pi为质数)

    所以通过枚举这些质因子求积,连乘的可以通过快速幂来实现,调用高精度乘法的次数减少了,只用在快速幂和幂的连乘,质因子只有根号个,复杂度就妥妥的降下来了,代码看下面↓

     const tt=1000000000;
     type arr=array[0..550]of int64;
     var n:longint;
         hash:array[0..16005]of longint;
         vis:array[0..16005]of boolean;
         ans:arr;
     procedure init;
      begin
       assign(input,'secret.in');reset(input);
       assign(output,'secret.out');rewrite(output);
       readln(n);
      end;
     procedure maker;
      var i,j:longint;
       begin
        fillchar(vis,sizeof(vis),1);
        for i:=2 to trunc(sqrt(2*n)) do
         for j:=2 to 2*n div i do
          if vis[i] then vis[i*j]:=false;
       end;
     function count(x,p:longint):longint;
      begin
       count:=0;
       while x>=p do
        begin
         inc(count,x div p);
         x:=x div p;
        end;
      end;
     function mul(a,b:arr):arr;
      var i,j:longint;
          c:arr;
      begin
       fillchar(c,sizeof(c),0);
       c[0]:=a[0]+b[0]-1;
       for i:=1 to a[0] do
        for j:=1 to b[0] do
         begin
          inc(c[i+j-1],a[i]*b[j]);
          inc(c[i+j],c[i+j-1] div tt);
          c[i+j-1]:=c[i+j-1] mod tt;
         end;
        if c[c[0]+1]>0 then inc(c[0]);
        while (c[c[0]]=0)and(c[0]>1) do dec(c[0]);
        exit(c);
       end;
     function power(a,b:longint):arr;
      var sum,w:arr;
       begin
        w[0]:=1;w[1]:=a;
        sum[0]:=1;sum[1]:=1;
        while b<>0 do
         begin
          if b and 1=1 then sum:=mul(sum,w);
          w:=mul(w,w);
          b:=b>>1;
         end;
        exit(sum);
       end;
     procedure main;
      var i:longint;
       begin
        maker;
        fillchar(hash,sizeof(hash),0);
        for i:=2 to n*2 do
         if vis[i] then hash[i]:=count(n*2,i)-count(n,i)-count(n+1,i);
        ans[1]:=1;ans[0]:=1;
        for i:=2 to 2*n do
         if hash[i]<>0 then ans:=mul(ans,power(i,hash[i]));
       end;
     procedure print;
      var i,j:longint;
          s:string;
      begin
       write(ans[ans[0]]);
       for i:=ans[0]-1 downto 1 do
        begin
         str(ans[i],s);
         for j:=length(s)+1 to 9 do write(0);
         write(s);
        end;
       close(input);close(output);
      end;
     begin
      init;
      main;
      print;
     end.

    所以就解好了

    【二】出栈次序

    一个栈(无穷大)的进栈序列为1,2,3,…,n,求不同的出栈序列个数

    和上面那个题也是一个思路,最后答案序列是卡特兰数

    类似的变形题有买票找零之类的

    题目描述长这样↓

    一场激烈的足球赛开始前,售票工作正在紧张的进行中,每张球票为 50 元,现有 2n 个人排队等待购票,其中有 n 个人手持 50 元的钞票,另外 n 个人手持 100 元的钞票,假设开始售票时售票处没有零钱,问 2n 个人有多少种排队方式,使售票处不至出现找不开钱的局面。

    我最开始做的时候是找规律,发现答案刚好是卡特兰数就写了,后来发现可以转换成进出栈的问题就是50元看成进栈,100元的时候把找回的50元看做一次出栈就一毛一样了。。

    【三】凸多边形的三角划分

    在一个凸多边形中,通过若干条互不相交的对角线,把这个多边形划分成了若干个三角形。求n多边形不同划分的方案数f(n)。

    题目变形有在圆上选择2n个点,将这些点成对连接起来使得所得到的n条线段不相交的方法数之类的

    【四】括号化

    矩阵连乘: P=A1*A2*A3*……*An,依据乘法结合律,不改变其顺序,只用括号表示成对的乘积,求不同括号化的方案数

    方案数是f(n-1),然而并没做过这类的题==

    好像就没有其他什么东西了诶

    【写的有漏洞的,欢迎路过大神吐槽】

    2016-08-09 22:35:33

    Ending.

  • 相关阅读:
    3305: Hero In Maze II (优先队列+bfs)
    2016年5月8日 GDCPC省赛总结
    POJ 2361 Tic Tac Toe
    about 字节
    KMP模式匹配
    scau 8616 汽车拉力比赛
    海盗分金--大于半数才成立
    scau 10692 XYM-入门之道
    函数模板和类模板成员函数的定义通常放在头文件中
    c语言运算符优先级巧记
  • 原文地址:https://www.cnblogs.com/wry0112/p/5754889.html
Copyright © 2011-2022 走看看