CCF认证201312第四题:有趣的数+动态规划专题

201312-4 有趣的数:
动态规划:每一步由上一步的最优解决定,大事化小
要点:找出状态位,找出边界值

当0~3 一位一位按序 填数字,0在1前面,2在3前面,0不能为最高位时,最多有6种状态

0状态:2 – 还剩 0、1、3
1状态:2 0 – 还剩 1、3
2状态:2 3 – 还剩 0、1
3状态:2 0 1 – 还剩 3
4状态:2 3 0 – 还剩 1
5状态:4位数全填 – 不剩

建立一个二维表t[n][6],行代表所求位数[i],列代表状态位[j]
t[i]取决于t[i-1],即在t[i-1][j]的基础上加1位数字

要达到i位数的0状态,即t[i][0],则是从t[i-1][0]状态得到(一直是1)
要达到i位数的1状态,从t[i-1][0](填0)和t[i-1][1](填2或0)
要达到i位数的2状态,即 t[i][2],则可从t[i-1][0](填3)和t[i-1][2](填3)得到
要达到i位数的3状态,即t[i][3],则可从t[i-1][1](填1)和t[i-1][3](填2或1)得到
要达到i位数的4状态,则可从t[i-1][1](填3)t[i-1][2](填0)和t[i-1][4](填3或0)得到
要达到i位数的5状态,则可从t[i-1][3](填3)t[i-1][4](填1)和t[i-1][5](填3或1)得到
t[i][5]就是求的恰好有i位的有趣数的个数(i>=4)

由于数值较大,可以每次计算状态位都取模

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        long mod = 1000000007;
        int n = 4;
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        long[][] t = new long[n+1][6];

        for(int i=1; i<=n; i++){
            int j = i-1;
            t[i][0] = 1;
            t[i][1] = (t[j][0] + t[j][1]*2)%mod;
            t[i][2] = (t[j][0] + t[j][2])%mod;
            t[i][3] = (t[j][1] + t[j][3]*2)%mod;
            t[i][4] = (t[j][1] + t[j][2] + t[j][4]*2)%mod;
            t[i][5] = (t[j][3] + t[j][4] + t[j][5]*2)%mod;
        }

        System.out.println(t[n][5]);
    }

}

最简单的动态规划问题:
台阶问题:每次只能上1级或者2级台阶,到第n级台阶共有多少种方法
思路:f(n) = f(n-1)+f(n-2) 其中n>2,即上第n级台阶的方法,是上n-1和n-2级台阶方法之和
f(1)和f(2)为边界
时间复杂度O(N),空间复杂度O(1)

public int f1(int n){
        int x1 = 1, x2 = 2;
        int temp = 0;

        if(n < 1){
            return 0;
        }
        if(n == 1){ //1级台阶,1种上法
            return x1;
        }
        if(n == 2){ //2级台阶,2种上法
            return x2;
        }

        for(int i=3; ireturn x1+x2;
    }

进阶问题:
金矿问题:有n个工人,m座矿山,每座矿山产量G[m],需要人力P[m],求最大总产量
思路:某一座矿山是否开采,取决于前面的矿山
建立一个二维表t,行是矿山数m,列是工人数n,值是总产量
状态位:
人数未使用(n < min(P[m])):人数不足新矿山:0;人数足够新矿山:G[i];
人数不足:t[m][n] = max(t[m-1][n-P[m]]+G[M], t[m-1][n])
要考虑 n-P[m]下标溢出(=-1)的情况
人数足够:t[m][n] = t[m-1][n-P[m]]+G[M]
只有一座矿山时的产量即为边界

public int[][] f2(int n, int m, int[]G, int[]P){
        int[][] t = new int[m][n]; //记录m座矿山,n个工人时的最大产量

        for(int j=0; j//初始化第一行
            if(j+1 >= P[0]){ //完全初始化第一行,注意工人数从0开始,0即1个工人
                t[0][j] = G[0];
            }
        }

        for(int i=1; i//外层矿山,内层工人
            for(int j=0; jint[] Pm = Arrays.copyOfRange(P, 0, i+1); //复制数组
                if(t[i-1][j] == 0 && j+1 >= P[i]){ //当前工人未使用。够新矿山所需人数
                    t[i][j] = G[i];
                }
                else if(j+1 == P[i-1] && G[i] > G[i-1] && j+1 == P[i]){ //当前工人已使用。新矿山在相同人数下产量更高
                    t[i][j] = G[i];
                }
                else if(j+1 < sum(P, i) && j+1 >= min(Pm) && j>=P[i]){ //人数不足,且能算入新矿山
                    t[i][j] = Math.max(t[i-1][j-P[i]]+G[i], t[i-1][j]);
                }
                else if(j+1 < sum(P, i) && j+1 >= min(Pm) && j+1>=P[i]){ //人数不足,全放新矿山刚好(放到上一个判断,下标会溢出)
                    t[i][j] =  G[i] > t[i-1][j]? G[i]:t[i-1][j];
                }
                else if(j+1 >= sum(P, i)){ //如果人数充足,则产量直接相加
                    t[i][j] = t[i-1][j] + G[i];
                }
                else{ // 当前工人已使用,新矿山相同人数下产量更低
                    t[i][j] = t[i-1][j];
                }
            }
        }

        return t;
    }

    public int min(int[]P){ //求已有矿山中所需最少工人数
        Arrays.sort(P); //升序排序
        return P[0];
    }
    public int sum(int[]P, int end){ //求已有矿山共需多少工人
        int sum = 0;
        for(int i=0; i<=end; i++){
            sum += P[i];
        }
        return sum;
    }

你可能感兴趣的:(备战ccf认证)