挑战程序竞赛系列(2):2.3优化递推关系式

来源:互联网 发布:数显温度控制器 单片机 编辑:程序博客网 时间:2024/05/16 18:43

2.3 优化递推关系式

详细代码可以fork下Github上leetcode项目,不定期更新。

练习题如下:
1. POJ 1742: Coins
2. POJ 3046: Ant Counting
3. POJ 3181: Dollar Dayz

POJ 1742: Coins

有n种面额的硬币,面额个数分别为A_i、C_i,求最多能搭配出几种不超过m的金额?

测试用例:

3 101 2 4 2 1 12 51 4 2 10 0

该dp很有意思,用boolean[][] dp,具有传递效果。递推式:

dp[i+1][j] |= dp[i][j-k*A[i]];

最后统计dp[n][1]~dp[n][m]中有多少个true即可。

代码如下:

public class SolutionDay03_P1742 {//  static int MAX_N = 100;//  static int MAX_M = 100000;//  static boolean[][] dp = new boolean[MAX_N+1][MAX_M+1];    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        while (true){            int n = in.nextInt();            int m = in.nextInt();            if (n == 0 && m == 0) break;            int[] A = new int[n];            int[] C = new int[n];            for (int i = 0; i < n;  i++){                A[i] = in.nextInt();            }            for (int i = 0; i < n;  i++){                C[i] = in.nextInt();            }//          for (int i = 0; i < dp.length;i++){//              Arrays.fill(dp[i], false);//          }            boolean[][] dp = new boolean[n+1][m+1];            dp[0][0] = true;            for (int i = 0; i < n; i++){                for (int j = 0; j <= m; j++){                    for (int k = 0; k <= C[i]; k++){                        if (k * A[i] <= j){                            dp[i+1][j] |= dp[i][j-k*A[i]];                        }                    }                }            }            int count = 0;            for (int i = 1; i <= m; i++){                if (dp[n][i]) count ++;            }            System.out.println(count);        }        in.close();    }}

很遗憾,TLE,更新可视图如下:
alt text

上述情况不算if条件的过滤,时间复杂度为O(ni(mki)),看下图:
alt text

如当i = 0, j = 3时,它也会有三次伪更新,但这三次更新是否有必要?显然完全不需要,因为我们知道dp[0][3] = 0。所以,程序很明显会TLE。

这里的主要原因在于,我们在计算下一阶段状态时,丢失了一些非常关键的信息。试想一下,我们能否在下一阶段带上前一阶段的硬币数?

观察上述更新过程,你会发现,它的更新很有特点,如从阶段0 -> 阶段1,有三次有效更新,而三次正是硬币可选择的个数0,1,2。同理,阶段1 -> 阶段2,两次有效更新,而硬币选择的个数为0,1。所以,给了我们一个启发,把硬币个数带入下一阶段,然后让它平行更新。

所以有了新的递推式:

dp[i][j] = c[i] // 表示当前能够更新的最大可能数。dp[i][j] = -1 // 表示当前无法再更新更新规则:// 上一阶段跳入下一阶段dp[i+1][j] = C[i], if dp[i][j] >= 0 // 说明当前面值可以在下一阶段递增// 越界(当前面值超过最大数m)或者当前阶段没有硬币可用dp[i+1][j] = -1, if j < A[i] || dp[i+1][j-A[i]] <= 0// 用掉一个硬币,递推式减1,直到-1dp[i+1][j] = dp[i+1][j-A[i]] - 1;

代码如下:

public class SolutionDay03_P1742 {//  static int MAX_N = 100;//  static int MAX_M = 100000;//  static boolean[][] dp = new boolean[MAX_N+1][MAX_M+1];    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        while (true){            int n = in.nextInt();            int m = in.nextInt();            if (n == 0 && m == 0) break;            int[] A = new int[n];            int[] C = new int[n];            for (int i = 0; i < n;  i++){                A[i] = in.nextInt();            }            for (int i = 0; i < n;  i++){                C[i] = in.nextInt();            }            System.out.println(solve(n, m, A, C));        }        in.close();    }    public static int solve(int n, int m, int[] A, int[] C){        int[][] dp = new int[n+1][m+1];        for (int i = 0; i < dp.length; i++){            Arrays.fill(dp[i], -1);        }        dp[0][0] = 0;        for (int i = 0; i < n; i++){            for (int j = 0; j <= m; j++){                if (dp[i][j] >= 0){                    dp[i+1][j] = C[i];                }                else if (j < A[i] || dp[i+1][j - A[i]] <= 0){                    dp[i+1][j] = -1;                }                else{                    dp[i+1][j] = dp[i+1][j-A[i]] - 1;                }            }        }        int answer = 0;        for (int i = 1; i <= m; i++){            if (dp[n][i] >= 0) answer++;        }        return answer;    }}

我们再来看看它的更新图,如下:
alt text

呵呵,MLE了,真是题目虐我千百遍,我待她如初恋啊,书上也说了,有些题就得重复利用数组。为啥可以?看上图,平行更新,且由if判断的顺序决定。代码如下:

    public static int solve(int n, int m, int[] A, int[] C){        int[] dp = new int[m+1];        Arrays.fill(dp, -1);        dp[0] = 0;        for (int i = 0; i < n; i++){            for (int j = 0; j <= m; j++){                if (dp[j] >= 0){                    dp[j] = C[i];                }                else if (j < A[i] || dp[j-A[i]] <= 0){                    dp[j] = -1;                }                else{                    dp[j] = dp[j-A[i]] - 1;                }            }        }        int answer = 0;        for (int i = 1; i <= m; i++){            if (dp[i] >= 0) answer++;        }        return answer;    }

POJ 3046: Ant Counting

蚂蚁牙黑,蚂蚁牙红:有A只蚂蚁,来自T个家族。同一个家族的蚂蚁长得一样,但是不同家族的蚂蚁牙齿颜色不同。任取n只蚂蚁(S<=n<=B),求能组成几种集合?

一道排列组合题,问题可以归简为:从元素为n个的集合中,取出m个元素,总共有多少种取法?

dp[i][j] 表示前i个家族中,取出j个元素的组合总数初始化:dp[0][0] = 1, 表示无蚂蚁,取出0个元素。(空集)

递推式:

dp[i][j]=k=0a[i]dp[i1][jk]

按书中的定义得,从前i个家族中取出j个,可以从前i1个家族中取出jk个,再从第i个家族中取出k个添加进来。

两点:

  • dp[i1][jk]的值代表了取jk个元素的组合数,每个组合都是唯一代表一个集合。
  • 当有k个相同的元素加入到新的集合中,就有了dp[i][j]=1×dp[i1][jk],因为第i个家庭的k个元素组合始终为1,该家族的每个元素无差别。

代码如下:

public class SolutionDay03_P3046 {    static final int MOD = 1000000;    static int[] family = new int[1000+16];    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        int T = in.nextInt();        int A = in.nextInt();        int S = in.nextInt();        int B = in.nextInt();        for (int i = 0; i < A; i++){            int index = in.nextInt();            ++family[index];        }        System.out.println(solve(T,A,S,B,family));        in.close();    }    public static int solve(int T, int A, int S, int B, int[] family){        int[][] dp = new int[T+1][10000 + 16];        dp[0][0] = 1;        int total = family[0];        for (int i = 1; i <= T; ++i){            total += family[i];            for (int k = 0; k <= family[i]; ++k){                for (int j = total; j >= k; --j){                    dp[i][j] = (dp[i][j] + dp[i-1][j-k]) % MOD;                }            }        }        int ans = 0;        for (int i = S; i <= B; ++i){            ans = (ans + dp[T][i]) % MOD;        }        return ans;    }}

上述时间和空间都不尽人如意,递推式优化:

dp[i][j]=k=0α[i]dp[i1][jk]=dp[i1][j]+k=1α[i]dp[i1][jk]=dp[i1][j]+k=0α[i]1dp[i1][jk1]=dp[i1][j]+k=0α[i]dp[i1][j1k]dp[i1][j1α[i]]=dp[i1][j]+dp[i][j1]+dp[i1][j1α[i]]

代码如下:

public static int solve(int T, int A, int S, int B, int[] family){        int[][] dp = new int[2][A+1];        dp[0][0] = dp[1][0] = 1;        int total = A;        for (int i = 1; i <= T; ++i){            for (int j = 1; j <= total; ++j){                if (j - 1 - family[i] >= 0){                    dp[i%2][j] = (dp[i%2][j-1] - dp[(i-1)%2][j-1-family[i]] + dp[(i-1)%2][j] + MOD) % MOD;                }else{                    dp[i%2][j] = (dp[i%2][j-1] + dp[(i-1)%2][j] ) % MOD;                }            }        }        int ans = 0;        for (int i = S; i <= B; ++i){            ans = (ans + dp[T%2][i]) % MOD;        }        return ans;    }

POJ 3181: Dollar Dayz

农夫约翰有N元钱,市场上有价值1……K的商品无限个,求所有的花钱方案?

dp[i][j] :用i种价格配出金额j的方案数初始化:dp[i][0] = 1; 用i中价格配出金额0的方案数为1递推式:dp[i][j] = dp[i-1][j] + dp[i-1][j-i] + dp[i-1][j-2*i] + ... + dp[i-1][0]如:1 * x + 2 * y = 5;y = 0, 1 * x = 5 y = 1, 1 * x = 3y = 2, 1 * x = 1dp[2][5] = dp[1][5] + dp[1][3] + dp[1][1]

代码如下:

public class SolutionDay04_P3181 {    public static void main(String[] args) {        Scanner in = new Scanner(System.in);        int N = in.nextInt();        int K = in.nextInt();        System.out.println(solve(N, K));        in.close();    }    public static BigInteger solve(int N, int K){        BigInteger[][] dp = new BigInteger[K + 1][N + 1];        for (int i = 0; i < dp.length; i++){            for (int j = 0; j < dp[0].length; j++){                dp[i][j] = BigInteger.ZERO;            }        }        dp[0][0] = BigInteger.ONE;        for (int i = 1; i <= K; ++i) {            for (int k = 0; k <= N; k += i) {                for (int j = N; j >= k; --j) {                    dp[i][j] = dp[i][j].add(dp[i - 1][j - k]);                }            }        }        return dp[K][N];    }}

递推式优化:

dp[i][j] = dp[i-1][j] + dp[i-1][j-i] + dp[i-1][j-2*i] + ... + dp[i-1][0];dp[i][j-i] = dp[i-1][j-i] + dp[i-1][j-2*i] + dp[i-1][j-3*i] + ... + dp[i-1][0];所以有:dp[i][j] = dp[i-1][j] + dp[i][j-i];

代码如下:

    public static BigInteger solve(int N, int K){        BigInteger[][] dp = new BigInteger[K + 1][N + 1];        for (int i = 0; i < dp.length; i++){            for (int j = 0; j < dp[0].length; j++){                dp[i][j] = BigInteger.ZERO;            }        }        dp[0][0] = BigInteger.ONE;        for (int i = 1; i <= K; ++i) {            //必须得初始化,dp[i][0]不能由dp[0][0]推得            dp[i][0] = BigInteger.ONE;            for (int j = 1; j <= N; ++j){                if (j < i){                    dp[i][j] = dp[i-1][j];                }                else{                    dp[i][j] = dp[i-1][j].add(dp[i][j-i]);                }            }        }        return dp[K][N];    }
0 0
原创粉丝点击