第三讲数学与简单DP

1.买不到的数目(完全背包👍)

在这里插入图片描述
在这里插入图片描述

思路:其实题意就是给你两个整数,可以随便用,和完全背包给你若干物品的重量,数量有无数个,去凑差不多,很多题目就是有基础的变形转换而来的,需要多灵活变通。

此题目还学到了一个结论:
给定a,b,若d=gcd(a,b) > 1则一定不能凑出最大数
结论:
如果a,b均是正整数且互质,那么由ax+by,x>=0,y>=0不能凑出的最大数是(a-1)(b-1)-1

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Main {
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int N = (int) (1e6 + 10);
	static boolean[] dp = new boolean[N];
	static int n = 0,m = 0,ans = 0;
	public static void main(String[] args) throws Exception{
		String[] nm = br.readLine().split(" ");
		n = Integer.parseInt(nm[0]);
		m = Integer.parseInt(nm[1]);
		
		dp[0] = true;
		for(int i = 1; i <= 1e6; i++) {
			if(i - n >= 0) {
				dp[i] |= dp[i - n];
			}
			if(i - m >= 0) {
				dp[i] |= dp[i - m];
			}
		}
		
		for(int i =(int) 1e6; i >= 1; i--) {
			if(!dp[i]) {
				System.out.println(i);break;
			}
			
		}
	}
}

完全背包的写法:(有两种物品的重量,凑)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 100;
int dp[N];
int n, m;
int a[3];
int main()
{
    cin >> n >> m;
    a[1] = n; a[2] = m;
    dp[0] = 1;
    for (int j = 1; j <= 2; j ++) {
        for (int i = a[j]; i <= 1e6; i ++) {
            dp[i] |= dp[i-a[j]];
        }
    }
    
    for (int i = 1e6 ; i >= 1; i --) {
        if (dp[i] == 0)  {
            cout <<  i;
            break;
        }
    }
}

下面这个题同样的做法:
在这里插入图片描述

结论👍👍👍

如果a,b均是正整数且互质,那么由ax+by,x>=0,y>=0,不能凑出来的最大数是ab-a-b((a-1)*(b-1))

2.蚂蚁感冒(数学)

在这里插入图片描述
在这里插入图片描述
思路:此题还是很简单的,我们可以看出只要分类讨论一下感冒的蚂蚁的方向和位置即可,如果感冒的蚂蚁方向是-的,那么我们需要首先看一下有没有一个位置比他小,然后然后是正的(为什么要这样呢,如果有的话,我们可以通过这个蚂蚁去感染那些方向也是-的蚂蚁,只有至少存在一个+方向的蚂蚁的时候,其他-方向的蚂蚁才可能被敢染),如果敢染的蚂蚁是+的同理。
❗:(1)如果感冒的蚂蚁是负方向的,我们必须先看是否存在比它位置小,且方向是+的,否则即使存在位置比它大的-的,也是无法感染的。
(2)注意细节,多想想各种情况就好啦😀


public class Main {
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int N = (int) (100);
	static int n = 0,m = 0,ans = 0;
	static int[] a = new int[N];
	public static void main(String[] args) throws Exception{
		n = Integer.parseInt(br.readLine());
		String[]aa = br.readLine().split(" ");
		for(int i = 1; i <= n; i++) {
			a[i] = Integer.parseInt(aa[i - 1]);
		}
		int p = a[1];
		if(p < 0) {
			for(int i = 1; i <= n; i++) {
				if(Math.abs(a[i]) < Math.abs(p) && a[i] > 0) {
					ans++;
				}
			}
			for(int i = 1; i <= n; i++) {
				if(ans != 0) {
					if(Math.abs(a[i]) > Math.abs(p) && a[i] < 0) {
						ans++;
					}
				}
			}
		}else {
			for(int i = 1; i <= n; i++) {
				if(Math.abs(a[i]) > p && a[i] < 0) {
					ans++;
				}
			}
			for(int i = 1; i <= n; i++) {
				if(ans != 0) {
					if(a[i] < p && a[i] > 0) {
						ans++;
					}
				}
			}
				
		}
		
		System.out.println(ans + 1);
		
	}
}

3.饮料换购(easy数学)

在这里插入图片描述
很明显刚开始有n瓶饮料,每3个可以换一个,减少3个的同时会增加一个瓶盖。

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    cin >> n;
    long long ans = n;
    while(n >= 3){
        ans += n/3;
        n = n - (n/3 *3) +n/3;
    }
    cout << ans;
}

法2:发现其他数学公式也可以直接求解:
起初有n个,我们每次3个可以换1瓶,也会多出一个瓶盖,很显然就消耗了2个瓶盖,但是我们必须保证有3瓶才可以兑换,如果最后只剩下2瓶饮料的时候显然不能兑换,为了处理这个问题,我们可以表达为(n-1)/2,当最后还剩2瓶的时候,肯定不足以换购一次

#include <iostream>

using namespace std;

int main()
{
    int n;
    cin >> n;
    cout << n + (n - 1) / 2 << endl;
    return 0;
}

拓展,如果不是需要3瓶,而是每e个换购1瓶的话,显然就是n + (n-1)/e;

4. 01背包

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int v[N],w[N];
int dp[N];
int n,vv;
int main(){
    cin >> n >> vv;
    for(int i = 1; i <= n; i++){
        cin >> v[i] >> w[i];
    }
    for(int i = 1; i <= n; i++){
        for(int j = vv; j >= v[i]; j--){
            dp[j] = max(dp[j - v[i]] +w[i],dp[j]); 
        }
    }
    cout << dp[vv];
}

5.摘花生(DP)

在这里插入图片描述
在这里插入图片描述

public class Main {
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int N = (int) (1e2 + 10);
	static int[][] a = new int[N][N];
	static int[][] dp = new int[N][N];
	static int r = 0, c = 0,k = 0;
	static long ans = 0;
	public static void main(String[] args) throws Exception{
		int t = Integer.parseInt(br.readLine());
		while(t-- > 0) {
			init();
			String[] rc = br.readLine().split(" ");
			r = Integer.parseInt(rc[0]);
			c = Integer.parseInt(rc[1]);
			for(int i = 1; i <= r; i++) {
				String[] aa = br.readLine().split(" ");
				for(int j = 1; j <= c; j++) {
					a[i][j] = Integer.parseInt(aa[j - 1]);
				}
			}
			for(int i = 1; i <= r; i++) {
				for(int j = 1; j <= c; j++) {
					dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]) + a[i][j];
				}
			}
			
			System.out.println(dp[r][c]);
		}
		
	}
	private static void init() {
		for(int i = 1; i <= r; i++) {
			for(int j = 1; j <= c; j++) {
				dp[i][j] = 0;
			}
		}
	}
}

6.最长上升子序列(DP)

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int dp[1010];
int a[1010];
int n;
int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    for(int i = 1; i <= n; i++){
        dp[i] = 1;
        for(int j = 1; j < i; j++){
            if(a[j] < a[i]){
                dp[i] = max(dp[i],dp[j] + 1);
            }
        }
    }
    int maxn = 0;
    //注意最长上升子序列的答案不一定是以最后结尾的!!!
    for(int i = 1; i <= n; i++){
        maxn = max(maxn,dp[i]);
    }
    cout << maxn;
}

7.地宫取宝👍👍(记忆化搜索/多维DP)

在这里插入图片描述
在这里插入图片描述
感觉这题确实有点复杂,哈哈哈。
法1:记忆化搜索
我们需要开一个f[x][y][maxn][num]数组记录在(x,y)位置最大价值为maxn,已经选取了num个宝物的方案数。
❗:需要特殊注意的是由于宝物的价值可能为0,所以我们初始化的时候,如果初始化为0的话,显然是不行的,因为如果第一个位置的宝物价值如果是0的话,我们相当于直接不选它了,显然是不对的。
如果初始赋值为负数的话,也是不可以的,下标不存在负数。所以 我们将所有的数总体全部+1(这样的话就不会存在价值为0的宝物了),这对方案个数是没有影响的。借鉴的博客

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;

public class Main {
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int mod = (int) (1e9 + 7);
	static int[][] a = new int[60][60];
	static long[][][][] f = new long[60][60][15][15];
	static int n = 0, m = 0,k = 0;
	static long ans = 0;
	public static void main(String[] args) throws Exception{
		String[] nmk = br.readLine().split(" ");
		n = Integer.parseInt(nmk[0]);
		m = Integer.parseInt(nmk[1]);
		k = Integer.parseInt(nmk[2]);
		for(int i = 1; i <= n; i++) {
			String[] aa = br.readLine().split(" ");
			for(int j = 1; j <= m; j++) {
				a[i][j] = Integer.parseInt(aa[j - 1]);
				a[i][j] += 1;
			}
		}
		init();
		System.out.println(dfs(1,1,0,0)%(long)mod);
	}
	private static long dfs(int x, int y, int maxn, int num) {
		if(f[x][y][maxn][num] != -1) {
			return f[x][y][maxn][num];
		}
		ans = 0;
		if(num > k || x > n || y > m) {
			return 0;
		}
		if(x == n && y == m) {
			if(num == k || num == k - 1 && a[x][y] > maxn) {
				return 1;
			}return 0;
		}
		if(a[x][y] > maxn) {
			ans = ans + dfs(x + 1, y,a[x][y], num + 1);
			ans = ans + dfs(x,y + 1,a[x][y],num + 1);
		}
		ans += dfs(x,y+1,maxn,num);
		ans += dfs(x+1, y, maxn, num);
		ans = ans % (long)mod;
		f[x][y][maxn][num] = ans;
		return ans;
	}
	private static void init() {
		for(int i = 0; i <= 50; i++) {
			for(int j = 0; j <= 50; j++) {
				for(int k = 0; k < 15; k++) {
					for(int c = 0; c < 13; c++) {
						f[i][j][k][c] = -1;
					}
				}
			}
		}
	}
	
}

8.波动数列()

在这里插入图片描述
思路:太难了…
在这里插入图片描述
因为x是任意整数,所以可以转换为s与(n-1)d1+(n-2)d2+…+dn-1模n的余数相等(此时s-((n-1)d1+(n-2)d2+…+dn-1)为n的整数倍)
下面开始分析dp:

(1)f[i][j]表示选择i个a或者-b余数为j的集合的数量;
(2)对于第i个数我们可以选择a也可以选择-b
选a的话=>((n-1)d1+(n-2)d2+,2*(dn-2)+a)%x = j;
((n-1)d1+(n-2)d2+,2*(dn-2))%x = j - a
选-b的话=>((n-1)d1+(n-2)d2+…+2*dn-2-b)%x=j
所以f[i][j]-d[i-1][j-(n-i)*a]
第i个选b的话:f[i][j]=f[i-1][j+(n-i)*b]

需要注意的是:取模的过程中可能出现负数,所以我们需要特殊处理一下

static int get_mod(int a, int b)
{
    return (a % b + b) % b;
}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class Main {
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	static int mod = 100000007;
	static int[][] f = new int[1010][1010];
	static int n = 0, s = 0,a = 0,b = 0;
	static long ans = 0;
	public static void main(String[] args) throws Exception{
		String[] nsab = br.readLine().split(" ");
		n = Integer.parseInt(nsab[0]);
		s = Integer.parseInt(nsab[1]);
		a = Integer.parseInt(nsab[2]);
		b = Integer.parseInt(nsab[3]);
		f[0][0] = 1;
		for(int i = 1; i < n; i++) {
			for(int j = 0; j < n; j++) {
				f[i][j] = (f[i - 1][get_mod(j - (n-i)*a, n)] + f[i - 1][get_mod(j+(n-i)*b, n)])%mod;
			}
		}
		System.out.println(f[n-1][get_mod(s, n)]);
	}
	
	static int get_mod(int a, int b)
	{
	    return (a % b + b) % b;
	}

	
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

释怀°Believe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值