题目:几道动态规划题

  

原文: https://blog.csdn.net/jacky_chenjp/article/details/63684427#%E9%A2%98%E7%9B%AE%E6%8F%8F%E8%BF%B0

 

  

分金子

题目描述:   A、B两伙马贼意外地在一片沙漠中发现了一处金矿,双方都想独占金矿,但各自的实力都不足以吞下对方,经过谈判后,双方同意用一个公平的方式来处理这片金矿。   处理的规则如下:他们把整个金矿分成n段,由A、B开始轮流从最左端或最右端占据一段,直到分完为止。   马贼A想提前知道他们能分到多少金子,因此请你帮忙计算他们最后各自拥有多少金子
?(两伙马贼均会采取对己方有利的策略) 输入: 测试数据包含多组输入数据。 输入数据的第一行为一个正整数T(T<=20),表示测试数据的组数。 然后是T组测试数据,每组测试数据的第一行包含一个整数n; 下一行包含n个数(n <= 500 ),表示每段金矿的含金量,保证其数值大小不超过1000。 输出: 对于每一组测试数据,输出一行"Case #id: sc1 sc2", 表示第id组数据时马贼A分到金子数量为sc1,马贼B分到金子数量为sc2。 详见样例。 样例输入: 2 6 4 7 2 9 5 2 10 140 649 340 982 105 86 56 610 340 879 样例输出: Case #1: 18 11 Case #2: 3206 981

 

 

    用dp[ L ][ R ]  表示,从l 到 r 段,所能获得的最大值

    则状态转移方程应该为 :

      dp(L)(R) = (L至R总和) - min{ dp(L+1)(R), dp(L)(R-1) } 

        (这里的 dp(L+1)(R),和 dp(L)(R-1)指的是给对方留下多少,所以尽量给对方留下少的)

      dp[L][R]  L=R时,显然等于此处数值

    用一个sum 数组记录从1 到 X 的总和,方便之后取各段总和

    对 dp[i][i] 赋值 为对应下标数据, 含义为对长度为1的段赋值

    之后开始循环,从dp[1][2]开始dp[2][3],dp[3][4] ... ...

      长度由小到大,长度为2结束后,再循环长度为3的情况 dp[1][3],dp[2][4]......  

      直到最后一次长度为n , 此时求得 dp[1][N] 

   代码:

  

import java.util.*;


public class Main{


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int round = sc.nextInt();
        for (int z = 0; z < round; z++) {
            int n = sc.nextInt();
            int[] a = new int[n + 1];
            int[] sum = new int[n + 1];
            int[][] dp = new int[n + 1][n + 1];
            sum[0] = 0;
            a[0] = 0;
            for (int i = 1; i <= n; i++) {
                a[i] = sc.nextInt();
                sum[i] = sum[i - 1] + a[i];
                dp[i][i] = a[i];
            }
            int k = 1;
            while (k <= n - 1) {
                for (int i = 1; i + k <= n; i++) {
                    dp[i][i + k] = sum[i + k] - sum[i - 1] -
                            Math.min(dp[i + 1][i + k], dp[i][i + k - 1]);
                }
                k++;
            }
            System.out.printf("Case #%d: %d %d\n" ,z+1,dp[1][n],sum[n]-dp[1][n]);
        }

    }
}

 

剪气球串

题目描述:   小明买了一些彩色的气球用绳子串在一条线上,想要装饰房间,每个气球都染上了一种颜色,每个气球的形状都是各不相同的。   我们用1到9一共9个数字表示不同的颜色,如12345则表示一串5个颜色各不相同的气球串。但小明希望得到不出现重复颜色的气球串,那么现在小明需要将这个气球串剪成多个较短的气球串,小明一共有多少种剪法?如原气球串12345的一种是剪法是剪成12和345两个气球串。   注意每种剪法需满足最后的子串中气球颜色各不相同(如果满足该条件,允许不剪,即保留原串)。两种剪法不同当且仅当存在一个位置,在一种剪法里剪开了,而在另一种中没剪开。详见样例分析。 输入: 第一行输入一个正整数n(
1≤n≤100000),表示气球的数量。 第二行输入n个整数a1,a2,a3...an,ai表示该气球串上第i个气球的颜色。对于任意i,有1≤ai≤9。 输出: 输出一行,第一行输出一个整数,表示满足要求的剪法,输出最终结果除以1000000007后的余数。 样例输入: 3 1 2 3 样例输出: 4

    思路:

      dp[L]表示 L长度的时候有多少种剪法。

      可以知道,如果没有重复的数字,dp[L] = dp[0]+dp[1]+...+dp[L-1]  (要提前给dp[0]设置成1)

      如果有重复的数字,设第一个重复数字从L向前数出现位置为X 则 dp[L] = dp[X] + dp[X+1] +...+dp[L-1]

      (注意,这里重复的数字不单纯考虑第 L 位上的数字,而是整体数字串)

      用一个十位的数组,每次记录是否数字已经出现过,没出现过则一直向前加和,出现了就停止。

      (注意,这里加和时 加的是 dp[J-1],所以当重复数字出现时,该重复数字的 dp[X]已经加到dp[L]中了)

      

public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        final int mod = 1000000007;
        int n = sc.nextInt();
        int[] a = new int[n+1];
        int[] dp = new int[n+1];for(int i=1;i<=n;i++){
            a[i]=sc.nextInt();
        }
        dp[0]=1;
        for(int i=1;i<=n;i++){
            int[] bo = new int[10];
            int j = i;
            while(j>0 && bo[a[j]]==0){
                dp[i]+=dp[j-1];
                bo[a[j]]=1;
                dp[i]%=mod;
                j--;
            }
        }
        System.out.println(dp[n]);

    }

 

 

爱奇艺-冒泡排序

题目描述 牛牛学习了冒泡排序,并写下以下冒泡排序的伪代码,注意牛牛排序的数组a是从下标0开始的。 BubbleSort(a): Repeat length(a)
-1 times: For every i from 0 to length(a) - 2: If a[i] > a[i+1] then: Swap a[i] and a[i+1] 牛牛现在要使用上述算法对一个数组A排序。 在排序前牛牛允许执行最多k次特定操作(可以不使用完),每次特定操作选择一个连续子数组,然后对其进行翻转,并且k次特定操作选择的子数组不相交。 例如A = {1, 2, 3, 4, 5, 6, 7}, k = 1,如果牛牛选择的子数组是[2,4](注意下标从0开始),那么翻转之后的数组变为A = {1, 2, 5, 4, 3, 6, 7}。 牛牛知道冒泡排序的效率一定程度上取决于Swap操作次数,牛牛想知道对于一个数组A在进行k次特定操作之后,再进行上述冒泡排序最少的Swap操作次数是多少? 输入描述 输入包括两行,第一行包括两个正整数n和k(2 ≤ n ≤ 50, 1 ≤ k ≤ 50),表示数组的长度和允许最多的特定操作次数。 第二行n个正整数A[i](1 ≤ A[i] ≤ 1000),表示数组内的元素,以空格分割。 输出描述 输出一个整数,表示在执行最多k次特定操作之后,对数组进行上述冒泡排序需要的Swap操作次数。 示例1 输入: 3 2 2 3 1 输出: 1

  

算法分析

看到题目,首先要思考,交换的次数跟什么有关,通过观察可以看出,冒泡排序的总交换次数等于数组中每一个元素的逆序数对的和,
所谓逆序数对就是排在该元素后面而且大于该元素的个数。所以问题就转化为对数组旋转不超过k次的条件下数组所有元素的逆序数对和最小。
所以我们每次进行旋转应该尽量使得旋转的数组逆序数对变小。这里我们采用动态规划的思想。用dp[i][j]表示前i个数总共旋转j次最多能够减少的逆序数对。
那么对于第i
+ 1个数,可以将前面任意一个数和第i+1个数进行旋转得到减少的逆序数对,也可以不旋转。所以可以得到递推公式: dp[I][J] = max(dp[I-1][J],shun[T][I] - NI[T][I]+dp[I-1][J-1]); 这里的shun[t][i]表示从t到i之间的逆序数对,ni[t][i]表示数组t到i的元素旋转后的逆序数对。
--------------------- 作者:YTea 来源:CSDN 原文:https://blog.csdn.net/ansizhong9191/article/details/88351889

 

代码:

import java.util.*;


public class Main {


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int n = sc.nextInt();
        int k = sc.nextInt();
        int[] a = new int[n + 1];

        int[][] dp = new int[n + 1][k + 1];

        for (int i = 1; i <= n; i++) {
            a[i] = sc.nextInt();
        }

        for (int i = 2; i <= n; i++) {
            //从第二个数开始遍历
            for (int j = 1; j <= k; j++) {
                // 遍历反转次数
                int mx = 0; // 记录 反转的话消灭的最大值

                for (int t = 1; t < i; t++) {
                    // 从1 到 i 依次确认 反转能消灭的逆序对数
                    int xiaomie = nixu(a, t, i) - shunxu(a, t, i);
                   // 更新最大值
                    mx = Math.max(mx, dp[t - 1][j - 1] + xiaomie);
                    // dp[t-1][j-1]含义为 前t -1 位 ,让出一次反转机会给当前段
                }
                // 此时,第 i 位与前方反转消灭最多逆序数已经确定,存储在mx
                // 比较当前位进行反转 与 不进行反转(dp[i-1][j])哪个收益更高
                dp[i][j] = Math.max(mx, dp[i - 1][j]);

            }
        }
        // dp[n][k]中存储着 n长度 k次反转能消灭的最大逆序对数,
        // 用总逆序数减去即为所求
        System.out.println(nixu(a, 1, n) - dp[n][k]);


    }

    static int nixu(int[] a, int l, int r) {
        int cnt = 0;
        for (int i = l; i <= r; i++) {
            for (int j = i + 1; j <= r; j++) {
                if (a[j] < a[i]) cnt++;
            }
        }
        return cnt;
    }

    static int shunxu(int[] a, int l, int r) {
        int cnt = 0;
        for (int i = l; i <= r; i++) {
            for (int j = i + 1; j <= r; j++) {
                if (a[j] > a[i]) cnt++;
            }
        }
        return cnt;
    }
}

 

转载于:https://www.cnblogs.com/xfdmyghdx/p/10643315.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值