文章目录
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;
}
}