题目
给定n个数字的序列,对位置i进行一次操作将使得都变成
特别的,对位置0进行操作将使得都变成
对位置进行操作将使得都变成
并且操作过位置i之后,位置0到i都不能再操作
设最多可以操作k(k≤n)次,最后得到的整个序列的总和最大可以是
你需要求出
方法一:四维动态规划
定义:表示第个位置进行第j次操作值为的情况下整个序列的值最多增加多少,最后一维为0表示位置i还没操作过,为1表示位置操作过
状态转移:考虑前i个位置进行j次操作,第个位置值为转移到->对前个位置进行或者次操作。
1.如果位置不进行操作,有两种转移:

  • 位置没有操作过,那么不会对位置造成任何影响,
  • 位置进行过操作,那么位置的数字和位置的数字一定是相等的(特殊考虑i=0,前面没有数字)。这个时候判断的大小,如果是较大,那么位置会变成并增加,即,否则位置和位置的值都会变成,(因为对位置i进行过操作),增加的贡献(对0特判)

2.如果位置进行操作,那么也有两种转移:

  • 位置i没有操作过,那么对的操作只能影响位置,判断一下 之间的关系并转移即可。

    如果是较大,那么i+1位置会变成,并增加,即
    否则

  • 位置进行过操作,那么对的操作可能会影响两个位置。和上面不操作的分支2进行相同的讨论即可。如果是较大,那么位置会变成并增加
    否则,位置和位置的值都会变成,

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 数字个数
     * @param a int整型一维数组 待操作序列
     * @return int整型一维数组
     */
    public int[] solve (int n, int[] a) {
        // write code here
        int sum = 0;
        //原数组的和
        for(int i = 0;i<n;i++){
            sum += a[i];
        }
        //dp[i][j][m][k]表示第i个位置进行j次操作值为m的情况下整个序列的值最多增加多少,最后一维为0表示位置i还没2操作,1表示位置i操作了
        int[][][][] dp = new int[200][201][201][2];
        //dp数组初始化
        for(int i =0;i<dp.length;i++){
            for(int j = 0;j<dp[0].length;j++){
                for(int m = 0;m<dp[0][0].length;m++){
                    for(int k = 0;k<dp[0][0][0].length;k++){
                        dp[i][j][m][k] = Integer.MIN_VALUE;
                    }
                }
            }
        }
        int[] ans = new int[n];
        dp[0][0][a[0]][0] = 0;
        dp[0][1][a[0]][1] = 0;
        //i枚举位置,j枚举操作次数
        for(int i = 0; i < n-1; ++i){
            for(int j = 0; j <= i+1; ++j){
                for(int x = a[i]; x < 201; ++ x){
                    //第i+1行
                    dp[i+1][j][a[i+1]][0] = Math.max(dp[i+1][j][a[i+1]][0], dp[i][j][x][0]);
                    //01
                    if(x > a[i+1]){
                        dp[i+1][j+1][x][1] = Math.max(dp[i+1][j+1][x][1], dp[i][j][x][0] + x-a[i+1]);
                    }else{
                        dp[i+1][j+1][a[i+1]][1] = Math.max(dp[i+1][j+1][a[i+1]][1], dp[i][j][x][0]+a[i+1]-x);
                    }
                    //10
                    if(x > a[i+1]){
                        dp[i+1][j][x][0] = Math.max(dp[i+1][j][x][0], dp[i][j][x][1]+x-a[i+1]);
                    }else{
                        dp[i+1][j][a[i+1]][0] = Math.max(dp[i+1][j][a[i+1]][0], dp[i][j][x][1]+(a[i+1]-x)+(i+1>1?1:0)*(a[i+1]-x));
                    }
                    //11
                    if(x > a[i+1]){
                        dp[i+1][j+1][x][1] = Math.max(dp[i+1][j+1][x][1], dp[i][j][x][1]+x-a[i+1]);
                    }else{
                        dp[i+1][j+1][a[i+1]][1] = Math.max(dp[i+1][j+1][a[i+1]][1], dp[i][j][x][1]+(a[i+1]-x)+(i+1>1?1:0)*(a[i+1]-x));
                    }
                }
            }
        }
        int mx = 0;
        for(int k = 1;k<=n;k++){
            for(int i = 0;i<=200;i++){
                for(int j= 0;j<2;j++){
                    mx = Math.max(mx,dp[n-1][k][i][j]);
                }

            }
            ans[k-1] = mx + sum;
        }

        return ans;
    }
}

复杂度:
时间复杂度:最外面两层循环时间复杂度是图片说明 ,里面一层时间复杂度是图片说明 所以总的时间复杂度是图片说明
空间复杂度:dp数组所占空间,图片说明

方法二:动态规划+滚动数组思想

  • 最大值=数组a的元素和+每次操作后增加量的最大值。因此可以一开始就求出数组a的元素和,因此我们可以定义数组,代表每次操作位置 后, 处的值为 时对应的最大总增量。
    状态转移方程为

  • 再借助队列做一个类似bfs的遍历,队列中记录的分别是元素下标、第几次操作、以当前元素为中的连续三个最大值。先用队列保存第一次在各个位置上操作后的结果,,然后对于每次操作,从队列中取出元素,向后扩展,更新最大值。

  • 设置两个,其中记录每次操作后的值,而是用来在bfs时找到当前操作的最大增加量,最后需要更新进中,的更新方式类似的
    第一次在各个位置操作后的表:
    图片说明

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 数字个数
     * @param a int整型一维数组 待操作序列
     * @return int整型一维数组
     */
    public int[] solve (int n, int[] a) {
        // write code here
       int[]ans=new int[n];
        //dp[i][j]表示每次操作i位置时i处的值j处对应的总增量
        int [][]dp=new int[n][201];
        //dp1用来在bfs中找到当前操作的最大增加量
        int[][]dp1=new int[n][201];
        for(int i=0;i<n;i++){
            Arrays.fill(dp[i],-1);
            Arrays.fill(dp1[i],-1);
        }
        Queue<Sta>q=new LinkedList<>();
        int sum=0;
        for(int i=0;i<n;i++){
            sum+=a[i];
            int t=a[i],originSum=a[i],cnt=1;
            //i不是第一个数
            if(i>0){
                t=Math.max(a[i-1],t);
                originSum+=a[i-1];
                cnt++;
            }
            //i不是最后一个数
            if(i<n-1){
                t=Math.max(a[i+1],t);
                originSum+=a[i+1];
                cnt++;//元素个数,边界处两个,非边界处3个
            }
            //用队列保存第一次在各个位置上操作后的结果,k=1
            q.add(new Sta(i,1,t));
            //在i位置操作后,t是最大值处的增量
            dp[i][t]=Math.max(dp[i][t],cnt*t-originSum);
            ans[0]=Math.max(cnt*t-originSum,ans[0]);
        }
        //bfs,继续操作n-1次
        for(int k=1;k<n;++k){
            int size=q.size();
            //枚举第一次操作后的结果
            for(int i=0;i<size;i++){
                Sta sta=q.poll();
                int pos=sta.pos,kk=sta.k,max=sta.max;
                //枚举下一次的操作位置,以后的每次操作位置都是向后扩展
                for(int j=pos+1;j<n;j++){
                    int max2,originSum,cnt=2;//扩展状态
                    if(j==pos+1){//向后扩展一位时
                        max2=max;
                        originSum=2*max;
                    }
                    else if(j==pos+2){//向后扩展两位时
                        max2=Math.max(max,a[j]);
                        originSum=max+a[j];
                    }else{
                        max2=Math.max(a[j-1],a[j]);
                        originSum=a[j-1]+a[j];
                    }if(j<n-1){
                        max2=Math.max(a[j+1],max2);
                        originSum+=a[j+1];
                        cnt++;
                    }
                    if(dp1[j][max2]==-1)q.add(new Sta(j,kk+1,max2));//状态不存在时新建状态,保存状态
                    //更新状态
                    dp1[j][max2]=Math.max(dp1[j][max2],cnt*max2-originSum+dp[pos][max]);
                    ans[kk]=Math.max(dp1[j][max2],ans[kk]);
                }
                dp[pos][max]=-1;//还原数组为-1,方便下一轮扩展用
            }
            //用dp1更新dp
            int[][]temp=dp;
            dp=dp1;
            dp1=temp;
        }
        //ans数组的每个元素为增加量最大值+原数组的和
        for(int i=0;i<n;i++){
            ans[i]+=sum;
        }
        return ans;
    }
    //pos是元素位置索引,k是第几次操作,max是当前连续三个数的最大值
    public class Sta{
        int pos;
        int k;
        int max;
        public Sta(int pos,int k,int max){
            this.pos=pos;
            this.k=k;
            this.max=max;
        }
    }
}

复杂度:
时间复杂度:遍历矩阵的时间,图片说明
空间复杂度:矩阵占用的空间,图片说明