二重dp,区间dp

二重背包

Problem Description
最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务。久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级。现在的问题是,xhd升掉最后一级还需n的经验值,xhd还留有m的忍耐度,每杀一个怪xhd会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到0或者0以下时,xhd就不会玩这游戏。xhd还说了他最多只杀s只怪。请问他能升掉这最后一级吗?Input
输入数据有多组,对于每组数据第一行输入n,m,k,s(0 < n,m,k,s < 100)四个正整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下来输入k行数据。每行数据输入两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪xhd会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)
Output
输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。、
Sample Input
10 10 1 10
1 1
10 10 1 9
1 1
9 10 2 10
1 1
2 2
Sample Output
0
-1
1

分析:dp里存的是最大经验值
注意: 最后扫的时候 外层循环为忍耐度,内层循环为杀怪个数,因为题目要求出剩余忍耐度最大,没有约束杀怪个数,一旦找到经验加满的即为最优解;
状态转移方程为: f[j][k]=max(f[j][k],f[j-v[i]][k-1]+w[i]); w[i]表示杀死第i个怪所得的经验值,v[i]表示消耗的忍耐度
我的dp是从小到大推的,
这个代码,,额 我改了两天了,还是不对,第二个测试样例过不了,崩溃啊,哪位大佬看到后帮我指下错误呀,,哭

import java.util.Scanner;

class Test48{
    static int n;
    static int m;//还剩的耐力
    static int K;
    static int s;
    static int flag = 0;
    static int dp[][] = new int[101][101];
    static int a[] = new int[101];//得到经验值
    static int b[] = new int[101];//耗费耐心
    static int num = Integer.MIN_VALUE;
    public static void main(String[] args) {
        while (true){
            Scanner sc = new Scanner(System.in);
            n = sc.nextInt();
            m = sc.nextInt();
            K = sc.nextInt();
            s = sc.nextInt();
            for(int i=1;i<=K;i++){

                a[i] = sc.nextInt();
                b[i] = sc.nextInt();
            }
            f();
        }
    }
    static void f(){//dp里是耐力和打怪个数

        for(int i = 1;i<=m;i++){//耐力值
            for(int j = 1;j<=K;j++){//怪的总数
                for(int k = 1;k<=s;k++){//可杀的怪的总数,,这个要是连续的呀,所以把他写到最里层循环比较好
                    if(b[j]<=i){
                        dp[i][k] = Math.max(dp[i][k],dp[i-b[j]][k-1]+a[j]);

                        if(dp[i][k]>=n){
                            num =Math.max(num,m-i);

                        }
                    }
                }

            }

        }
    if(num== Integer.MIN_VALUE){
        System.out.println("-1");
    }else {
        System.out.println(num);
    }
    }
}

区间dp:

决策区间由一个大区间分成几个小区间,通常套路是三个循环,第一个循环是区间长度,第二个循环是起始点的位置,第三个循环是那个变量j,

矩阵最优连乘

前提:一个n乘m的矩阵成一个m乘p的矩阵的运算量为mnp,所以矩阵乘法中A(BC)和(AB)C的运算量是不一样呢的,可以举个例子自己试一下,现在给出n个矩阵,并输入n+1个数,第i个矩阵是a[i-1]*a[i],求出最少的运算量

思路:考虑最后一次做乘法的位置,也就是最后最大的两个矩阵相乘的位置,这会将原问题分成两个规模较小的子问题


static int Dp_2(int l,int r){
        for(int i = l;i<r;i++){
            dp[l][r] = Math.min(dp[l][r],dp[l,i]+dp[i+1,r]+a[l-1]*a[i]*a[r]);
        }
        return dp[1][n];
    }

大致就是这样的函数啦

括号匹配

求最长的括号匹配
((()))答案是6
([][][)答案是6
思路:这就是把他分成小区间,若s括号匹配,t括号匹配,那么st括号匹配,
对于区间[l,r]如果sl = sr ,则可由区间【l+1,r-1】转移得到+2得到
也可以用另一种转移方式,枚举中间间断点,m,该区间由[l,m]和[m+1,r]的组合

而且有枚举区间的长度,区间长度也是从1开始

 static int match(int x,int y){
        x = s[x];
        y  =s[y];
        return x=='('&& y==')' || x=='['&& y ==']';
    }
    static void f{
        for(int i = 1;i<n;i++){
            dp[i][i+1] = match(i,i+1);//处理一下长度只有2的左边还有右边边界问题
        }
        for(int d = 2;d<n;d++){//枚举区间长度
            for(int i = 1,j;(j=i+d)<=n;i++){
       //         dp[i][j] = dp[i+1][j-1]+match(i,j);//第一种写法
                for(int k = i;k<j;k++){//第二种写法
                    dp[i][j] = Math.max(dp[i][j],dp[i][k]+dp[k+1][j]);
                }
            }
        }
    }

石子合并

有n堆石子排成一排,现将n堆石子合成一堆,合并过程只能将相邻两堆合成一堆,每次花费为这两堆数字之和,经过n-1次后称为一堆,求总花费最小
如何不要求相邻两堆,任取两堆石子怎么做?
分析: 如果不相邻的话那就很简单的小根堆啊,每次选最小的两个,试试就知道为什么
那如果必须为相邻呢?在哪里约束这个条件呢?那就根矩阵连乘是一样的,左边一个打的合并了,右边一个大的合并了,然后左右合并
f [ i ] [ j ] = m i n ( f [ i ] [ k ] + f [ k + 1 ] [ j ] ) + a [ i ] + , , , , + a [ j ] f[i][j] = min(f[i][k]+f[k+1][j])+a[i]+,,,,+a[j] f[i][j]=min(f[i][k]+f[k+1][j])+a[i]+,,,,+a[j]
a[i]到[j]可以与处理前缀和

同样,第一个循环是区间长度,第二个是区间起点,第三个是里面多少个

for(int d = 1;d<n;d++){
	for(int i = 1;j;(j= i+d)<=n;i++){
		f[i][j] = INT_MAX;
		for(int k =i;k<j;k++){
			f[i][j] =min(f[i][j]+f[j+1][k])+s[j]-s[i-1];
		}
	}
}

如果石子不是一条线了,而是排成一个环 那怎么班
当然可以枚举这个环断开的位置,然后在里面继续上面的操作,那样的复杂度是四次方
思路:考虑将原石子复制一份在后面,即令an+i = ai,在长度为2n的石子串上做区间dp  所有n个长度为n的区间中,合并代价最小的那个为答案,这样就可以把复杂度降到n的三次方了??????不太懂

决策单调性

这个,,,我还理解不了,大概就是记录一下决策点的意思

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值