ACwing 蓝桥杯 第三讲 数学与简单DP(java)

AcWing 1205. 买不到的数目

小明开了一家糖果店。

他别出心裁:把水果糖包成4颗一包和7颗一包的两种。

糖果不能拆包卖。

小朋友来买糖的时候,他就用这两种包装来组合。

当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。

你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。

大于17的任何数字都可以用4和7组合出来。

本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。

输入格式
两个正整数 n,m,表示每种包装中糖的颗数。

输出格式
一个正整数,表示最大不能买到的糖数。

数据范围
2≤n,m≤1000,
保证数据一定有解。

输入样例:

4 7

输出样例:

17

思路1:(不知道公式的情况下)

1、先尽力分析,
2、打表找规律
首先分析题目:由xp + yq 不能凑出来的最大的数是什么
d = (p , q ) >1 如果 p q 的最大公约数大于1,就是无解的,例如,6 2 的公约数是2 ,那他们凑出来的数一定是2的倍数,只要是一个奇数,就一定凑不出来,故无解。这时已经分析得差不多了,但问题还没解决,接下来,使用打表找规律的方式(也就是写暴力)来找解决方法。
在这里插入图片描述

代码(不能ac主要是看dfs)

这个代码主要是练习一下不知道公式时如何暴力破解。

import java.util.Scanner;

public class Main {
    
    static boolean dfs(int m, int q, int p) {
        if (m == 0) return true;
        if (m >= q && dfs(m - q, q, p)) return true;
        if (m >= p && dfs(m - p, q, p)) return true;
        return false;
    }
    
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int q = in.nextInt();
        int p = in.nextInt();
        int res = 0;
        for (int i = 1; i < 1000; i ++) {//假设遍历到1000,我们认为后面的数都是可以凑出来的
            if(!(dfs(i, q, p))) res = i; 
        }
        System.out.print(res);
    }
}
代码
import java.util.Scanner;

public class Main {
    
    public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int a = sc.nextInt();
		int b = sc.nextInt();
		sc.close();
		System.out.println(a*b-a-b);
		
	}
}

AcWing 1211. 蚂蚁感冒

长 100 厘米的细长直杆子上有 n 只蚂蚁。

它们的头有的朝左,有的朝右。

每只蚂蚁都只能沿着杆子向前爬,速度是 1 厘米/秒。

当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。

这些蚂蚁中,有 1 只蚂蚁感冒了。

并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。

请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。

输入格式
第一行输入一个整数 n, 表示蚂蚁的总数。

接着的一行是 n 个用空格分开的整数 Xi, Xi 的绝对值表示蚂蚁离开杆子左边端点的距离。

正值表示头朝右,负值表示头朝左,数据中不会出现 0 值,也不会出现两只蚂蚁占用同一位置。

其中,第一个数据代表的蚂蚁感冒了。

输出格式
输出1个整数,表示最后感冒蚂蚁的数目。

数据范围
1<n<50,
0<|Xi|<100
输入样例1:

3
5 -2 8

输出样例1:

1

输入样例2:

5
-10 8 -20 12 25

输出样例2:

3

思路

可以把两只蚂蚁的相撞掉头看作是直接穿过去,效果是一样的,但会容易实现
1、小性质:调头等于穿过
在这里插入图片描述

代码

import java.util.Scanner;

public class Main {
    static final int N = 50;
    static int n;
    static int[] a = new int[N];
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        n = in.nextInt();
        for (int i = 0; i < n; i ++ ) a[i] = in.nextInt();
        
        int left = 0, right = 0;
        for (int i = 1; i < n; i ++ ) {
            if(Math.abs(a[i]) > Math.abs(a[0]) && a[i] < 0) right ++;
            else if (Math.abs(a[i]) < Math.abs(a[0]) && a[i] > 0) left ++;
        }
        if (a[0] > 0 && right == 0) System.out.print(1);
        else if (a[0] < 0 && left == 0) System.out.print(1);
        else System.out.print((left + right + 1));
        
    }
}

上取整

在这里插入图片描述

AcWing 1216. 饮料换购

乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去(但不允许暂借或赊账)。

请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的 n 瓶饮料,最后他一共能喝到多少瓶饮料。

输入格式
输入一个整数 n,表示初始买入的饮料数量。

输出格式
输出一个整数,表示一共能够喝到的饮料数量。

数据范围
0<n<10000
输入样例:

100

输出样例:

149

代码

import java.util.Scanner;

public class Main {
    static final int N = 10010;
    static int n, res;
    static Scanner in = new Scanner(System.in);
    
    public static void main(String[] args) {
        n = in.nextInt();
        res = n;
        while (n >= 3) {
            res += (n / 3);
            n = (n / 3) + (n % 3);
        }
        System.out.print(res);
    }
}

AcWing 2. 01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

思路(二刷)

背包问题都可以看成是组合模型,在一堆物品里挑一些出来,在满足某种限制的情况下,所有组合里面最好的组合是什么。不考虑顺序的情况下,选出最好的组合
看题目有两个不同维度,一个是每一个物品的体积,一个是物品的价值
在这里插入图片描述

(核心)集合:所有只从前i个物品中选(限制1),且总体积不超过j的所有方案的集合(限制2),所有方案的描述(dp的初始化、边界,都是从这个含义来做的)

状态计算:(化整为零)从前i个物品中选,且总体积不超过j的最大价值,所以最终答案就是f[N][V] 他的含义就是,所有从N个物品中选,且总体积不超过V的最大价值
一般来说都会把集合划分,f[i,j]用椭圆表示,求椭圆里面的,每种方案的最大价值,划分成不重不漏的集合,如何划分?
划分依据: 找最后一个不同点,相当于选第i个物品,
左边:所有从前i个物品中选,且总体积不超过j的且不包含第i物品所有方案
——>所有从前i - 1 个物品中选,且总体积不超过j的所有方案; ——>f[i -1][j];
在这里插入图片描述
右边:前面选什么样都行,但必须包含i,一般把这个分成两大部分,第一部分是变化的变化的部分,第二部分是只包含第i个物品的部分(不变)
1、将所有的方案同时减去第i个物品
2、求减去后的最大值
3、最后,最大值+第i个物品。
具体表达式:f[i - 1][j - v[i]] + w[i]

闫式DP分析法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当j < vi 时需要特判一下

代码1(一般写法)

import java.io.*;

public class Main {
    static final int N = 1010;
    static int[][] f = new int[N][N];
    static int[] w = new int[N];//价值
    static int[] v = new int[N];//体积
    static int n, m;
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    
    public static void main(String[] args) throws Exception {
        in.nextToken();
        n = (int)in.nval;
        in.nextToken();
        m = (int)in.nval;
        for (int i = 1; i <= n; i ++ ) {
            in.nextToken();
            v[i] = (int)in.nval;
            in.nextToken();
            w[i] = (int)in.nval;
        }
        
        for (int i = 1; i <= n; i ++ ) {
            for (int j = 0; j <= m; j ++ ) {
                f[i][j] = f[i - 1][j];
                if(j >= v[i]) f[i][j] = Math.max(f[i][j],f[i - 1][j - v[i]] + w[i]);
            }
        }
        System.out.print(f[n][m]);
        //为什么f[n][m]是最终答案呢?因为我们要求的是在n个物品中选出不超过体积m的最大价值,所以最后的答案就是f[n][m]
        //但是当我们要求的是在n个物体中选出体积恰好为m的物品集合的价值最大时,最后的答案就不一定是f[n][m]了;
    }
}

代码2(对上面的代码进行优化,但只能优化空间,时间并没有优化)

思路

步骤一:
把f数组转为一维。不要第一维了只需要f[j],然后,看一下下面的代码,

for (int i = 1; i <= n; i ++ ) {
            for (int j = 0; j <= m; j ++ ) {
                f[j] = f[j];//这一步是恒等式,也可以看作是f[i][j] = f[i - 1][j],这一行没有什么意义了可以删掉
                if(j >= v[i]) f[j] = Math.max(f[j],f[j - v[i]] + w[i]);
                /*
                	在这里我们主要判断一下f[j - v[i]]是在第几个i算出来的
                	假设现在已经遍历到第i层了,有如下情况
                	由于j是从小到大遍历的,然后j - v[i]一定比j小,所以在算f[j]之前,f[j - v[i]]已经算好了,所以这个f[j - v[i]]是在第i层算好的.所以现在的f[j - v[i]]相当于f[i][j - v[i]],和原来的f[i - 1][j - v[i]]明显不能等效替换。所以我们要进行步骤二;
                	23.03.27:理解更新:上面那句话可以理解为,一共有 n 条 j 轴(想象成一个二维表格),每条 j 轴上的 f[j] 都是由 前面已经算好的 f[j - v[i]] 算出来的,也就是说,其实 f[j - v[i]] 只能调用同一层的之前算好的某个 f[k] (k = j - v[i])无法调用上一层(i - 1 层)的f[k],因为少了一维,按照目前的遍历顺序是无法记忆上一层的 f[k] 的
                	题外话:以前也理解到了这种程度了,但是过了很久没接触之后,就忘了,以前的努力和时间都白白浪费了,所以说理解了真不等于记住了,大脑只能记住经常使用的东西,如果不经常使用,大脑就会遗弃,所以以后学到的新知识以及获得的新理解能用东西记下来就最好记下来,如果没记下来还忘记了,努力就白费了。
                */
            }
        }

步骤二:
将j从大到小遍历,即可与f[i - 1][j - v[i]]等效替换,具体看代码

for (int i = 1; i <= n; i ++ ) {
            for (int j = m; j >= v[i]; j -- ) {//由于删掉了f[j] = f[j];里面只剩一个if 可以把if的条件放入循环条件
                f[j] = Math.max(f[j],f[j - v[i]] + w[i]);
                /*
					为什么把j从大到小开始遍历就可以等效替换了呢?我们来分析一下。
					当现在外层循环循环到第i层时:
						因为 j > j - v[i],我们要算f[j] 就必须先算等式右边的f[j - v[i]],因为j从大到小遍历,此时的f[j - v[i]]只能从上一层,也就是从i - 1 层“借过来”,所以此时的f[j - v[i]] 可以等效的看为 f[i - 1][j - v[i]]
				*/
            }
完整代码
import java.io.*;

public class Main {
    static final int N = 1010;
    static int[] f = new int[N];
    static int[] w = new int[N];//价值
    static int[] v = new int[N];//体积
    static int n, m;
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    
    public static void main(String[] args) throws Exception {
        in.nextToken();
        n = (int)in.nval;
        in.nextToken();
        m = (int)in.nval;
        for (int i = 1; i <= n; i ++ ) {
            in.nextToken();
            v[i] = (int)in.nval;
            in.nextToken();
            w[i] = (int)in.nval;
        }
        
        for (int i = 1; i <= n; i ++ )
            for (int j = m; j >= v[i]; j -- ) f[j] = Math.max(f[j],f[j - v[i]] + w[i]);
            
        System.out.print(f[m]);
    }
}

AcWing 1015. 摘花生

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。

在这里插入图片描述

输入格式
第一行是一个整数T,代表一共有多少组数据。

接下来是T组数据。

每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。

每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。

输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。

数据范围
1≤T≤100,
1≤R,C≤100,
0≤M≤1000
输入样例:

2
2 2
1 1
3 4
2 3
2 3 4
1 6 5

输出样例:

8
16

思路

时间复杂度:每组数据的长宽是100,所以每个测试数据的时间复杂度大概在1万左右,100组就是100万,所以时间复杂度大概是n^2或者n左右,所以我们的状态表示最大是2维的

有两种方案,一种是往下走到[i, j]的走法的集合,一种是从左往右走到[i,j]的走法的集合
在这里插入图片描述

第一种:从[1,1]不管你怎么走都行,倒数第二的点都必须走到[i - 1, j] 然后再往下走到[i, j]
第二种:从[1, 1] 不管你怎么走,倒数第二个的点必须是[i, j - 1],然后再往右走到[i , j]
在这里插入图片描述

我们先把相同的部分去掉,然后求前面的max ,最后再加回最后一个,左边右边是对称的求法,只要知道左边的公式就可以得到右边的公式。

闫式dp分析法yyds;
在这里插入图片描述

代码

import java.io.*;

public class Main {
    static final int N = 110;
    static int n,m;
    static int[][] f = new int[N][N];
    static int[][] w = new int[N][N];
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    
    public static void main(String[] args) throws Exception {
        in.nextToken();
        int T = (int)in.nval;
        while (T -- > 0) {
            in.nextToken();
            n = (int)in.nval;
            in.nextToken();
            m = (int)in.nval;
            for (int i = 1; i <= n; i ++ )
                for (int j = 1; j <= m; j ++){
                    in.nextToken();
                    w[i][j] = (int)in.nval;
                }
            for (int i = 1; i <= n; i ++ )
                for(int j = 1; j <= m; j ++ ) 
                    f[i][j] = Math.max(f[i - 1][j],f[i][j - 1]) + w[i][j];
            System.out.println(f[n][m]);
        }
    }
}

AcWing 895. 最长上升子序列

给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式
第一行包含整数N。

第二行包含N个整数,表示完整序列。

输出格式
输出一个整数,表示最大长度。

数据范围
1≤N≤1000,
−109≤数列中的数≤109
输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

思路

时间复杂度:看题知道n <= 1000, 时间复杂度不超过n^2,这道题,实际使用f[i]就可以了
把状态的每一位带到问题里面去,就可以得出我们想要的集合了
所以f[i]可以表示:所有以i为结尾的严格单调上升的子序列集合里面长度的最大值
然后我们就要找到最后一个不同的元素,在这道题里面,这里的最后一个元素就是每个子序列的最后一个,但我们发现,在f[i]这个集合里面,最后一个元素都是i自己,所以i这个元素并不是最后一个不同的元素,所以我们可以找倒数第二个元素,他就是最后一个不同的点,也是我们的划分依据,
首先第一种情况就是,倒数第二个元素不存在,那么我们就用0表示
以此类推。。。。
最多能划分到i - 1类,所以可以最多可以划分i个类
分别求一下每一类的最大值然后取一个max就可以了,
首先,第一类,表示只有i自己一个数,他的长度是确定的,就是1,f[i] = 1
从集合的含义出发:假设我们倒数第二个元素是k,看一下这一类的所以子序列是什么样的,然后再去分析他的最大值
在这里插入图片描述

我们可以继续使用之前的思想,就是先把相同的部分去掉,然后求变化的部分,最后在加上不变的部分。
在这里插入图片描述
去掉a[i]之后,前面就是以ak为结尾的严格单调上升的子序列的集合,他的最大值就存在 了f[k] + 1(这里的1就是ai的长度)这里有一点需要注意:其实每一类都不一定存在,当ak >= ai 时就不存在,相当于是空集,所以在代码里,需要加一个判断,不能大于ai,最后答案就是在所有集合里取一个max,
初始化的问题,这题不需要初始化,(f[1]也可以看做是初始化)

代码

import java.util.*;

public class Main {
    static final int N = 1010;
    static int n;
    static int[] w = new int[N];
    static int[] f = new int[N];
    static Scanner in = new Scanner(System.in);
    
    public static void main(String[] args) {
        n = in.nextInt();
        for (int i = 1; i <= n; i ++ ) w[i] = in.nextInt();
        
        for (int i = 1; i <= n; i ++ ) {
            f[i] = 1;
            for (int j = 1; j <= i; j ++ ) {
                if (w[j] < w[i]) f[i] = Math.max(f[i],f[j] + 1);
            }
        }
        int res = 0;
        for (int i = 1; i <= n; i ++ ) res = Math.max(res,f[i]);
        System.out.print(res);
    }
    
}

AcWing 1214. 波动数列

观察这个数列:

1 3 0 2 -1 1 -2 …

这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数。

栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?

输入格式
共一行,包含四个整数 n,s,a,b,含义如前面所述。

输出格式
共一行,包含一个整数,表示满足条件的方案数。

由于这个数很大,请输出方案数除以 100000007 的余数。

数据范围
1≤n≤1000,
−109≤s≤109,
1≤a,b≤106
输入样例:
4 10 2 3
输出样例:
2
样例解释
两个满足条件的数列分别是2 4 1 3和7 4 1 -2。

思路

n <= 1000; 时间复杂度为n^2;
列出表达式:整理转换:集合映射:
在这里插入图片描述
分析到这里,其实可以发现这就是一个组合类型的问题,和01背包一样,01背包也是组合类型的问题,可以用01背包的思想来做。
因为每个方案的最后一项都是一样的,我们就可以把它给去掉,只看前面的项
在这里插入图片描述
在这里插入图片描述

总结

以后只要是问不同选法的最大值或数量的,都可以当作是组合问题(可以用01背包模型 来求解)。

代码

import java.util.*;

public class Main {
    static final int N = 1010, MOD = 100000007;
    static int[][] f = new int[N][N];
    static int n, s, a, b;
    static Scanner in = new Scanner(System.in);
    
    static int getMod(int x, int y) {
        return (x % y + y) % y;
    }
    
    public static void main(String[] args) {
        n = in.nextInt();
        s = in.nextInt();
        a = in.nextInt();
        b = in.nextInt();
        
        f[0][0] = 1;
        for (int i = 1; i < n; i ++ ) 
            for (int j = 0; j < n; j ++ )
                f[i][j] = (f[i - 1][getMod(j - (n - i) * a, n)] + f[i - 1][getMod(j + (n -i) * b, n)]) % MOD;
    
        System.out.print(f[n - 1][getMod(s, n)]);
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值