挑战编程-第六章-组合数学-总结

挑战编程-第六章-组合数学-总结
    学习用java处理大数很有必要,解组合数学的题就是一个找公式推规律的过程。推公式的过程
又类似于推导状态转移方程,再推出公式后,往往会发现题目的数据是超long long的,再用大数
方法来处理,代码很随意就能上200行,但是如果用java来写的话,推出公式,组合数学的题就是水
题。
Uva 10183:
    Meaning:
        给定两个整数,统计区间[a,b]内有多少个斐波那契数。
    Thinking:
        没有什么特殊的地方,由斐波拉切数的通项公式可估算出,在题目给出的数据范围内最多有60个左右的斐波拉切数,所有可直接处理出来,然后询问。
    Code:
        
<pre name="code" class="java">import java.math.BigInteger;
        import java.util.Scanner;
        public class Main
        {
            public static void main(String[] args)
            {
                Scanner cin = new Scanner(System.in);
                BigInteger[] f = new BigInteger[600];
                f[0] = new BigInteger("1");
                f[1] = new BigInteger("2");
                for(int i = 2; i < 600; i ++)
                    f[i] = f[i - 1].add(f[i - 2]);
                for(;;)
                {
                    BigInteger a, b;
                    int res = 0;
                    a = cin.nextBigInteger();
                    b = cin.nextBigInteger();
                    if(a.compareTo(BigInteger.ZERO) == 0 && b.compareTo(BigInteger.ZERO) == 0)
                        break;
                    for(int i = 0; i < 600; i ++)
                        if(f[i].compareTo(a) != -1 && f[i].compareTo(b) != 1)
                            res ++;
                    System.out.println(res);
                }
            }
        }

 Uva 10213:    Meaning:        椭圆上n个点,连成n*(n-1)/2条线段,最多可以把椭圆分成多少个部分。    Thinking:        公式:F(n) = C(n, 2) + C(n, 4) + 1        http://en.wikipedia.org/wiki/Dividing_a_circle_into_areas        http://www.arbelos.co.uk/Papers/Chords-regions.pdf    Code:       
 
  import java.math.BigInteger;
        import java.util.Scanner;
        public class Main
        {
            public static void main(String[] args)
            {
                Scanner cin = new Scanner(System.in);
                int t;
                t = cin.nextInt();
                BigInteger a = new BigInteger("6");
                BigInteger b = new BigInteger("18");
                BigInteger c = new BigInteger("23");
                BigInteger d = new BigInteger("24");
                while(t-- > 0)
                {
                    BigInteger N = cin.nextBigInteger();
                    BigInteger ans;
                    ans = (N.multiply(N).multiply(N).multiply(N).subtract(a.multiply(N.multiply(N).multiply(N))).add(c.multiply(N.multiply(N))).subtract(b.multiply(N)).add(d)).divide(d);
                    System.out.println(ans);
                }
            }
        } 



Uva 10198:
    Meaning:
        给出一个数n,问有多少个数的数字之和恰好为n。数字只包括1、2、3、4且1和4等价。
    Thinking:
        因为只有四个数字,所以用test[n]表示:有test[n]个数的数字之和恰好为n。
        所以test[n - 1]表示:有test[n - 1]个数的数字之和恰好为n - 1;
            test[n - 2]表示:有test[n - 2]个数的数字之和恰好为n - 2;
            test[n - 3]表示:有test[n - 3]个数的数字之和恰好为n - 3;
            test[n - 4]表示:有test[n - 4]个数的数字之和恰好为n - 4;
        所以test[n] = test[n - 1] + test[n - 2] + test[n - 3] + test[n - 4];
        又因为数字4和1等价,所以test[n - 1]等于test[n - 4];
        所以test[n] = 2 * test[n - 1] + test[n - 2] + test[n - 3];
    Code:
     
   import java.math.BigInteger;
        import java.util.Scanner;
        public class Main004
        {
            public static void main(String[] args)
            {
                Scanner cin = new Scanner(System.in);
                BigInteger[] test = new BigInteger[1002];
                test[1] = new BigInteger("2");
                test[2] = new BigInteger("5");
                test[3] = new BigInteger("13");
                for (int i = 4; i <= 1000; i++)
                {
                    test[i] = test[i - 1].add(test[i - 1]).add(test[i - 2]).add(test[i - 3]);
                }
                int n;
                while (cin.hasNext())
                {
                    n = cin.nextInt();
                    System.out.println(test[n]);
                }
            }
        }


Uva 10157:
    Meaning:
        给出括号表达式的长度n,和深度d;求长度为n,深度为d的合法表达式的个数。长度和深度的定义题目均以给出。
    Thinking:
        由题意可知,当括号表达式的长度为奇数是,满足条件的表达式个数必定为0,同时若深度超过n个括号所能得到的合法表达式的最大深度时,结果也为0;所以,当长度为偶数时,用f[m][d]表示:括号对数为m,深度不超过d的
        合法表达式的总数;假设E是一个深度为d,括号对数为m的合法表达式,则E的最左边的括号l,一定和某个右括号r匹配,把他们看做一组分界线,则这对括号将表达式分成两个部分,括号里面的和括号右边的。
        E = ( X ) Y
        假设左边部分由k对括号,则右边部分就有m - k -1对括号,表达式X的深度为d-1,表达式Y的最大深度为d,所以,括号对数为m,深度为d的合法表达式的总数为 f[m][d] - f[m][d - 1]。
        而且f[m][d] = f[k][d - 1] * f[m - k - 1][d], 0 <= k <= m - 1
    Code:
     
   import java.math.BigInteger;
        import java.util.Scanner;
        public class Main
        {
            public static void main(String[] args)
            {
                Scanner cin = new Scanner(System.in);
                BigInteger[][] f = new BigInteger[160][160];
                for (int i = 0; i <=150; i++)
                {
                    f[0][i] = new BigInteger("1");
                }
                for (int i = 1; i <= 150; i++)
                {
                    for (int j = 0; j <= 150; j++)
                    {
                        f[i][j] = new BigInteger("0");
                    }
                }
                for (int i = 1; i <= 150; i++)
                {
                    for (int j = 1; j <= 150; j++)
                    {
                        for (int k =0; k < i; k++)
                        {
                            f[i][j] = f[i][j].add(f[k][j - 1].multiply(f[i - k -1][j]));
                        }
                    }
                }
                while (cin.hasNext())
                {
                    int n = cin.nextInt(), d = cin.nextInt();
                    if (n % 2 == 1)
                        System.out.println("0");
                    else
                    {
                        n >>= 1;
                        System.out.println(f[n][d].add(f[n][d - 1].negate()));
                    }
                }
            }
        }



Uva 10247:
    Meaning:
        给出完全k叉树的深度d和分支因子k,统计有多少种方案给一棵完全k叉树中的每个节点标号。标号规则是:每个节点的标号小于它所有后代的标号。对于一颗n个节点的树,标号范围是(1, 2, 3, ···, n - 1, n)。
    Thinking:
        由题意知:根节点的标号一定是最小的,用f[i][j]表示:i叉树,j层时的方案数;用g[i][j]表示:i叉树,j层时的节点数;C(m, n)表示:m中取n的组合数。根节点用掉最小的一个标号后,还剩g[i][j] - 1 个标号,要将它分给i颗子树,每颗子树得到g[i][j - 1]个标号,且g[i][j] - 1 = i * g[i][j - 1]。
            根据排列组合规律可以得出公式f[i][j] = f[i][j - 1] * C(k * g[i][j - 1], g[i][j - 1]),1 <= k <= i
    Code:
        
import java.math.BigInteger;
        import java.util.Scanner;
        public class Main
        {
            public static void main(String[] args)
            {
                Scanner cin = new Scanner(System.in);
                BigInteger[][] f = new BigInteger[30][30];
                int[][] g = new int[30][30];
                for(int i = 1; i <= 21; i++)
                {
                    f[i][0] = new BigInteger("1");
                    g[i][0] = 1;
                    for(int j = 1; i * j <= 21; j++)
                    {
                        f[i][j] = new BigInteger("1");
                        g[i][j] = i * g[i][j - 1];
                        g[i][j]++;
                        for(int k = i; k >= 1; k--)
                        {
                            f[i][j] = f[i][j].multiply(f[i][j - 1].multiply(C(k * g[i][j - 1], g[i][j - 1])));
                        }
                    }
                }
                while(cin.hasNext())
                {
                    int k = cin.nextInt();
                    int d = cin.nextInt();
                    System.out.println(f[k][d]);
                }
            }
            public static BigInteger C(int m, int n)
            {
                if(m - n < n)
                    n = m - n;
                BigInteger res = new BigInteger("1");
                for(int i = 1; i <= n; i++)
                    res = res.multiply(BigInteger.valueOf(m - i + 1)).divide(BigInteger.valueOf(i));
                return res;
            }
        }


Uva 10254:
    Meaning:
        四根柱子的汉诺塔问题,移动规则是标准汉诺塔的移动策略。给出盘子的数量N,求出最少的移动步数。
    Thinking:    
        先把最小的圆盘移动到第四个柱子上,然后用标准汉诺塔的策略把剩下的N - 1个圆盘移动到目标柱子上,再把最小的圆盘移动到目标柱子上。然后打表会发现规律,相邻连个数的差是2的次幂,且2的m次幂将出现n + 1次。模拟增加的过程,预处理出所有数据。
    Code:
      
 import java.math.BigInteger;
        import java.util.Scanner;
        public class Main
        {
            public static void main(String[] args)
            {
                Scanner cin = new Scanner(System.in);
                BigInteger[] ans = new BigInteger[10009];
                BigInteger d = new BigInteger("1");
                ans[0] = new BigInteger("0");    
                int n, k = 0, ci = 1;
                for (int i = 1; i <= 10000; i++)
                {
                    ans[i] = ans[i - 1].add(d);
                    k++;
                    if (k == ci)
                    {
                        k = 0;
                        ci++;
                        d = d.multiply(BigInteger.valueOf(2));
                    }
                }
                while (cin.hasNext())
                {
                    n = cin.nextInt();
                    System.out.println(ans[n]);
                }
            }
        }



Uva 10049:
    Meaning:
        自描述序列是唯一一个具有如下性质的不下降正整数子序列:对于任意正整数k,改序列恰好包含f(k)个k。对于给定的n,计算f(n)的值。
    Thinking:
        这个题最能体现出,组合数学题推公式类似于推状态转移方程的特点。这个序列的规律很明显,可以直接模拟f(n)增长的过程,但是1 <= n <= 2 000 000 000,所以本题的数据范围内不能直接模拟。但f(n)的增长缓慢,可以利用它的反函数g(f(n)) =  n,但这个函数出现了一对多的情况,所以修改后g(f(n)) = max{n | f(n) = n},然后再求一次反函数f(n) = min{k | g(k) >= n},即可得到f(n)。由表可推出公式f(n) = max{f-1(n)} - max{f-1(n-1)},即f(n) = g(n) - g(n - 1);
    Code:
        #include <stdio.h>
        #include <algorithm>
        using namespace std;
        const int M = 700000;
        long long g[M];
        int main()
        {
            int n, i;
            for (g[1] =1, g[2] = i = 3; i < M; i++)
            {
                g[i] = g[i - 1] + (lower_bound(g + 1, g + i, i) - g);
            }
            while (scanf("%d", &n), n)
            {
                printf("%d\n", lower_bound(g + 1, g + M, n) - g);
            }
            return 0;

        }


Uva 846:
    Meaning:
        按照题目给出的规则,从数轴上的x走到y,最少需要多少步。
    Thinking:
        算出前几个数后就能找到规律。
    Code:
      
  #include <stdio.h>
        #include <math.h>
        int main()
        {
            int T;
            scanf("%d", &T);
            while (T--)
            {
                int n, m;
                scanf("%d%d", &n, &m);
                int dis = m - n;
                if (!dis)
                {
                    printf("0\n");
                    continue;
                }
                int s = (int)sqrt((double)dis);
                if (s * s == dis)
                    s = 2 * s - 1;
                else if (s * (s + 1) < dis)
                    s = 2 *s + 1;
                else
                    s *= 2;
                printf("%d\n", s);
            }
            return 0;
        }




java写入文件:
        try
        {
            File test = new File("test.txt");
            PrintStream out = new PrintStream(new FileOutputStream(test));
            System.setOut(out);
        }
        catch(Exception e){}








你可能感兴趣的:(java,组合数学,大数,找规律)