动态规划+贪心

一、动态规划

1. 01背包问题

🤠 思路
在这里插入图片描述
🤠 二维数组dp
🤠 参考传送门
🤠 时间 O( nm ) 空间 O( nm )

/* 输入案例  输出 8
	4 5
	1 2
	2 4
	3 4
	4 5
	*/
	static int N = 1010;
	static int[][] f = new int[N][N];// 集合数组
	static int[] v = new int[N];// 物品体积数组 volume
	static int[] w = new int[N];// 物品价值数组 weight

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();// 物品个数
		int m = sc.nextInt();// 背包容量
		for (int i = 1; i < n; i++)// 物品下标从 1 开始
		{
			v[i] = sc.nextInt();
			w[i] = sc.nextInt();
		}

//		填集合数组
		for (int i = 1; i <= n; i++)// 注意 物品数 从 1 开始
			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.println(f[n][m]);
	}

🤠 滚动数组
🤠 参考传送门
🤠 时间 O( nm ) 空间 O( m )

	static int N = 1010;
	static int[] f = new int[N];
	static int[] v = new int[N];// 物品体积数组 volume
	static int[] w = new int[N];// 物品价值数组 weight

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();// 物品个数
		int m = sc.nextInt();// 背包容量
		for (int i = 1; i < n; i++)// 物品下标从 1 开始
		{
			v[i] = sc.nextInt();
			w[i] = sc.nextInt();
		}

//		填集合数组
		for (int i = 1; i <= n; i++)// 注意 物品数 从 1 开始
//		从后面往前枚举, j >= v[i] 防止越界
			for (int j = m; j >= v[i]; j--)
			{
				f[j] = Math.max(f[j], f[j - v[i]] + w[i]);
			}
		System.out.println(f[m]);
	}

2. 完全背包问题

🤠 一个物品可以重复用
🤠 参考传送门
🤠 1.0版本,时间:O( nmm),空间:O( nm)

	static int N = 1010;
	static int[][] f = new int[N][N];
	static int n, m;
	static int[] v = new int[N];
	static int[] w = new int[N];

	/* 输入数据  输出:10
	4 5
	1 2
	2 4
	3 4
	4 5
	 */
	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();

		for (int i = 1; i <= n; i++)
		{
			v[i] = sc.nextInt();
			w[i] = sc.nextInt();
		}

		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				for (int k = 1; k <= j / v[i]; k++)
				{
					f[i][j] = f[i - 1][j];
					if (j >= v[i])
					{
						f[i][j] = Math.max(f[i][j], f[i][j - v[i] * k] + k * w[i]);
					}
				}
		System.out.println(f[n][m]);
	}

🤠 2.0 版本 ,时间:O( nm),空间:O( nm)

	static int N = 1010;
	static int[][] f = new int[N][N];
	static int n, m;
	static int[] v = new int[N];
	static int[] w = new int[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();

		for (int i = 1; i <= n; i++)
		{
			v[i] = sc.nextInt();
			w[i] = sc.nextInt();
		}

		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)

				if (j >= v[i])
				{
					f[i][j] = Math.max(f[i - 1][j], f[i][j - v[i]] + w[i]);
//						f[i][j] = Math.max(f[i][j], f[i][j - v[i] * k] + k * w[i]);
				}

		System.out.println(f[n][m]);
	}

🤠 3.0 版本,滚动数组优化版,时间:O( nm ),空间:O(m)


	static int N = 1010;
	static int[] f = new int[N];
	static int n, m;
	static int[] v = new int[N];
	static int[] w = new int[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();

		for (int i = 1; i <= n; i++)
		{
			v[i] = sc.nextInt();
			w[i] = sc.nextInt();
		}

		for (int i = 1; i <= n; i++)
			for (int j = v[i]; j <= m; j++)

				f[j] = Math.max(f[j], f[j - v[i]] + w[i]);

		System.out.println(f[m]);
	}

2. 多重背包问题

🤠 参考传送门
🤠 暴力破解法 O(nms)

	static int n, m, N = 1010;
	static int[] v = new int[N];// 体积
	static int[] w = new int[N];// 价值
	static int[] s = new int[N];// 价值

	static int[][] f = new int[N][N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();

		for (int i = 1; i <= n; i++)
		{
			v[i] = sc.nextInt();
			w[i] = sc.nextInt();
			s[i] = sc.nextInt();
		}

//		暴力解法
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				for (int k = 1; k <= s[i] && k * v[i] <= j; k++)
					f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - k * v[i]] + k * w[i]);

		System.out.println(f[n][m]);
	}

🤠 二进制优化版 O(nm logs)
🤠 例
在这里插入图片描述

	static int N = 25000, M = 2010; //25000 = 2000 * log 2000
	static int n, m;
	static int[] v = new int[N];
	static int[] w = new int[N];
	static int[] f = new int[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		int cnt = 0;// 记录选择方案 个数

//		把所有的方案打包成一个,转换成 01 背包的形式
		for (int i = 1; i <= n; i++)
		{
			int a, b, s;
			a = sc.nextInt();
			b = sc.nextInt();
			s = sc.nextInt();

			int k = 1;
			while (k <= s)
			{
				cnt++;
				v[cnt] = a * k;
				w[cnt] = b * k;

				s -= k;
				k *= 2;
			}
			if (s > 0)
			{
				cnt++;
				v[cnt] = a * s;
				w[cnt] = b * s;
			}
		}

		n = cnt;

//		对 01 背包 进行处理
		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.println(f[m]);
	}

3. 分组背包

🤠 有N 组物品和一个容量是V 的背包。每组物品有若干个,同一组内的物品最多只能选一个
🤠 原题传送门
🤠 参考传送门
🤠 朴素版

	static int N = 110;
	static int n, m;
	static int[] s = new int[N];// 第 i 组物品的个数
	static int[][] v = new int[N][N];// 体积
	static int[][] w = new int[N][N];// 价值

	static int[][] f = new int[N][N];

	public static void main(String[] ars)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();// n组物品
		m = sc.nextInt();

		for (int i = 1; i <= n; i++)
		{
			s[i] = sc.nextInt();
			for (int j = 1; j <= s[i]; j++)
			{
				v[i][j] = sc.nextInt();
				w[i][j] = sc.nextInt();
			}
		}

		for (int i = 1; i <= n; i++)// n组物品
			for (int j = 0; j <= m; j++)// 背包容量
			{
				f[i][j] = f[i - 1][j];// 不选的情况
				for (int k = 1; k <= s[i]; k++)//枚举每组中的每一个物品 
				{
					if (v[i][k] <= j)
						f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
				}
			}
		System.out.println(f[n][m]);
	}

4. 线性DP

🤠 数字三角形
🤠 参考传送门

🤠 由上往下版

	static int N = 510, n;
	static int INF = (int) 1e9;

	static int[][] a = new int[N][N];// 存数字
	static int[][] f = new int[N][N];// 存方案

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= i; j++)
				a[i][j] = sc.nextInt();

//		初始化边界的情况
		for (int i = 0; i <= n; i++)// 求 1 列时,会用到 0 列的数据,所以要把 0 列初始化
			for (int j = 0; j <= i + 1; j++) // 求 j 列时会用到 j+1 列,所以得初始化到 i+1 列
				f[i][j] = -INF;

		f[1][1] = a[1][1];// 初始化起点
		for (int i = 2; i <= n; i++)// 从起点的下一行开始DP
			for (int j = 1; j    <= i; j++)
				f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - 1]) + a[i][j];

		int res = -INF;
		for (int i = 1; i <= n; i++)// 遍历底部所有节点取出最大值
			res = res > f[n][i] ? res : f[n][i];

		System.out.println(res);

	}

🤠 由下往上版

	static int N = 510, n;
	static int[][] a = new int[N][N];
	static int[][] f = new int[N][N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= i; j++)
				a[i][j] = sc.nextInt();

//		求上一行要用到的所有下一行的元素都与 a 有相关,所以无须考虑边界问题
		for (int i = n; i >= 1; i--)
			for (int j = i; j >= 1; j--)
				f[i][j] = Math.max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];

		System.out.println(f[1][1]);
	}

🤠 最长上升子序列
🤠 参考传送门

	static int N = 1010, n;
	static int[] a = new int[N];
	static int[] f = new int[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		for (int i = 1; i <= n; i++)
			a[i] = sc.nextInt();

		for (int i = 2; i <= n; i++)
		{
			f[i] = 1;// 在前边找不到小于自身的数,默认长度为1
			for (int j = 1; j < i; j++)//从第一个开始找,找到最大的一个 f[j]+1
				if (a[i] > a[j])//保证子序列的上升性
					f[i] = Math.max(f[j] + 1, f[i]);
		}

		int res = 1;
		for (int i = 1; i <= n; i++)
			res = res > f[i] ? res : f[i];

		System.out.println(res);
	}

🤠 最长公共子序列
🤠 参考传送门

	static int N = 1010, n, m;
	static int[][] f = new int[N][N];
	static char[] a = new char[N];
	static char[] b = new char[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		String s1 = sc.next();
		String s2 = sc.next();

		for (int i = 1; i <= n; i++)
			a[i] = s1.charAt(i - 1);

		for (int i = 1; i <= n; i++)
			b[i] = s2.charAt(i - 1);

		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]);
				if (a[i] == b[j])
					f[i][j] = Math.max(f[i][j], f[i - 1][j - 1] + 1);
			}

		System.out.println(f[n][m]);
	}

5. 区间动态规划

🤠 合并石子
🤠 原题地址
🤠 石子合并代价计算过程

左堆的总数量 : s[k]-s[l-1]
右堆的总数量 : s[r]-s[k+1-1]
合并左右堆得代价 : s[k]-s[l-1]+s[r]-s[k+1-1] = s[r]-s[l-1]
	static int N = 310, n;
	static int[][] f = new int[N][N];
	static int[] s = new int[N];// 记录前 i 堆的石子和

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		for (int i = 1; i <= n; i++)
		{
			s[i] = sc.nextInt();
			s[i] += s[i - 1];
		}

		for (int len = 2; len <= n; len++)// 枚举区间长度
			for (int i = 1; i + len - 1 <= n; i++)
			{
				int l = i;// 区间左边界
				int r = i + len - 1;// 区间右边界
				f[l][r] = (int) 1e9;
				for (int k = l; k <= r - 1; k++)
					f[l][r] = Math.min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
			}

		System.out.println(f[1][n]);
	}

6. 状态压缩DP

🥞 蒙德里安的梦想(原题)

🥞 核心思想:求出横着放的所有情况,竖着放已经根据横着放的位置定了

🥞 参考视频

	static int N = 12;
	static int M = 1 << N;// 2的12次方
	static int n, m;
	// 第一维表示列,第二维是(方案)一个二进制数表示哪个格子放了一个横着的长方块
	static long[][] f = new long[N][M];
	// 存储每种状态是否有奇数个连续的0,偶数个 (合法) 为 true
	static boolean[] st = new boolean[M];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		while (true)
		{
			n = sc.nextInt();
			m = sc.nextInt();
			if (n == 0 && m == 0)
				break;
//			预处理 st 数组,找出不能放 格子 的情况
			for (int i = 0; i < 1 << n; i++)// i 二进制数 表示状态(方案)
			{
//				记录一列中 0 的个数
				st[i] = true;
				int cnt = 0;
				for (int j = 0; j < n; j++)// 枚举此方案的每一行,判断是否有放置有空格
				{
//					判断当前行有没有放方块
					if ((i >> j & 1) != 0)// 进来即是在 i>>j 行上放了方块
					{
//						奇数 & 1 = 1 (奇数的二进制表示最低位一定是 1)
						if ((cnt & 1) != 0)// 当前连续的空格为奇数个
						{
							st[i] = false;// i 方案不合法
//因为是下往上单调性枚举的,只要竖排空格出现奇数个,即非法,后续的行怎么填也不会弥补这个奇数gap
							break;
						}
					} else
					{
						cnt++;// 记录 0 的个数
					}
				}
				// 判断最后几个格子(高位 0 )是否出现奇数空格的情况
				if ((cnt & 1) != 0)
					st[i] = false;
			}

//			多组输入数据,所以得初始化全局变量
			for (int i = 0; i < f.length; i++)
			{
				Arrays.fill(f[i], 0);
			}
			// 棋盘从第0列开始,没有从-1列延伸出来的方格
			// 因此所有小长方形都是竖着放置

//			初值 
			f[0][0] = 1;// 从 -1 列推出 0 列 只有竖着放一种方案 合法
			for (int i = 1; i <= m; i++)// 枚举每一列
			{
				for (int j = 0; j < 1 << n; j++)// 枚举第i列的每一种状态
				{
					for (int k = 0; k < 1 << n; k++)// 遍历第 i-1 列的所有状态
					{
//						此时 j 定 k 不定
//						if:就是 前一列的 k 方案 和 当前列的 j 方案 融合成当前列, 判断是否合法
						if ((j & k) == 0 && st[j | k])
						{
							// j & k == 0表示没有重合 伸出部分,即不允许i-1列伸出的同时i-2列也伸出
							// st[j | k]表示当前的连续空格都为合法偶数
							// j | k : 就是第i列的方案,i-1列伸出来的 k ,当前列的选的 j
								
							// i列 j 方案合法,且已经定了,方案数就是 i-1 列 k方案 的 方案数
							f[i][j] += f[i - 1][k];
						}
					}

				}
			}
//			最后一列伸出 0 个方块的方案就是答案
			System.out.println(f[m][0]);
		}
	}

🥞 最短哈密顿路(原题)

🥞 O(20 * 20 * 2^20)4亿 4秒

	static int M = 1 << 20;//状态数
	static int N = 21;
	static int[][] f = new int[M][N];// M 是一个二进制的整数,每一位表示两个状态 1 / 0(经过 / 不经过),N 表示终点
	static int[][] w = new int[N][N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
//		读取数据
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				w[i][j] = sc.nextInt();

		for (int i = 0; i < f.length; i++)
			Arrays.fill(f[i], 0x3f3f3f3f);

		f[1][0] = 0;//状态是 000...01, 起点在 0,最短路径是 0 (没动)

		for (int i = 0; i < (1 << n); i++)// 枚举每一种方案
			for (int j = 0; j < n; j++)// 枚举方案的每一位
				if ((i >> j & 1) == 1)// 判断 i 方案是否经过 j 点
					for (int k = 0; k < n; k++)// 枚举 j 点的前一个点
//						if ((i - (1 << j) >> k & 1) == 1) //把 i 的第 j 位置为 0(下同)
						if ((i ^ (1 << j) >> k & 1) == 1) // 未经过点 j 时,经过 点k 的情况
//					求出到 k 点的最小值,而 k 到 j 是固定的 w[k][j],所以相当于求出 到 j 点 的最小值
							f[i][j] = Math.min(f[i][j], f[i - (1 << j)][k] + w[k][j]);

//		注意 加减运算符 优先级 比 位运算 高
		System.out.println(f[(1 << n) - 1][n - 1]);// 经过所有点的状态,终点是 n-1 就是答案

	}

7. 树形DP

🥞 没有上司的舞会

	static int N = 6010;
	static int[] happy = new int[N];// 幸福度数组
	static int[] e = new int[N];// 节点的值
	static int[] ne = new int[N];// next指针数组
	static int[] h = new int[N];// 头指针数组
	static int n, idx;
	static boolean[] hasFather = new boolean[N];
	static int[][] f = new int[N][2];

//	a 是上司,  b 是下属,头插法
	static void add(int a, int b)
	{
		e[idx] = b;
		ne[idx] = h[a];
		h[a] = idx++;
	}

	static void dfs(int u)
	{
		f[u][1] = happy[u];// 选了 u 节点,加上 u 节点的happy值
		for (int i = h[u]; ~i != 0; i = ne[i])// 遍历树的子树(链表),~(-1)= 0
		{
			int j = e[i];// j 是 u 的子树
			dfs(j);// 继续深搜 子树 j
			f[u][0] += Math.max(f[j][1], f[j][0]);
			f[u][1] += f[j][0];
		}
	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		for (int i = 1; i <= n; i++)
		{
			happy[i] = sc.nextInt();
		}

		Arrays.fill(h, -1);// 将头结点全部置为 -1
		for (int i = 1; i < n; i++)
		{
			int l, k;// k 是 l 的上司(根)
			l = sc.nextInt();
			k = sc.nextInt();
			hasFather[l] = true;
			add(k, l);
		}
		int root = 1;
		while (hasFather[root])
			root++;// 找到没有父节点的根节点

		dfs(root);
//		在选与不选根节点中挑出最大值输出
		System.out.println(Math.max(f[root][0], f[root][1]));
	}

👨‍🏫 补充:按位取反: ~


public class 按位取反
{
	public static void main(String[] args)
	{
		System.out.println(~0);// -1
		System.out.println(~-1);// 0

		/*		
		按位取反:符号位也变,对计算机存储的补码进行操作
		反码:符号为不变
		~(-1)
		1000 0001 原码 -1
		0111 1110 反码 -1
		0111 1111 补码 -1
		1000 0000 补码 ~(-1)			
		*/
	}
}

8. 记忆化搜索

🥞 参考传送门

	static int N = 310;
	static int n, m;
	static int[][] h = new int[N][N];// 高度数组
	static int[][] f = new int[N][N];

	static int[] dx =
	{ -1, 0, 1, 0 };// 左右移动数组
	static int[] dy =
	{ 0, 1, 0, -1 };// 上下移动数组

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= m; j++)
				h[i][j] = sc.nextInt();

//		初始化状态数组
		for (int i = 0; i < f.length; i++)
			Arrays.fill(f[i], -1);
		int res = 0;
		for (int i = 1; i < n; i++)
			for (int j = 1; j < m; j++)
			{
				res = Math.max(res, dp(i, j));
			}

		System.out.println(res);
	}

//	递归DP 填记忆表
	static int dp(int x, int y)
	{
//		是否曾经就查过此状态的最长滑雪距离
		if (f[x][y] != -1)
			return f[x][y];

		f[x][y] = 1;
		for (int i = 0; i < 4; i++)// 枚举四个方向
		{
			int xx = x + dx[i];
			int yy = y + dy[i];
			// 判断这个点是否越界和高于原点
			if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && h[xx][yy] < h[x][y])
				f[x][y] = Math.max(f[x][y], dp(xx, yy) + 1);// 枚举所有方案取最大值
		}
		return f[x][y];
	}

9. 数位DP

🥞 原题链接

🥞 超短写法

//	计算整数 n 有多少位
	static int dgt(int n)
	{
		int res = 0;
		while (n != 0)
		{
			res++;
			n /= 10;
		}
		return res;
	}

	static int cnt(int n, int i)// 计算从 1 到 n 的整数中数字 i 出现的次数
	{
		int res = 0;
		int d = dgt(n);// d 存的是 n 的位数
		for (int j = 1; j <= d; j++)// 从右向左枚举 n 的每一位上 i 出现的次数
		{
//			假设 n = abc(dj)efg
			int p = (int) Math.pow(10, j - 1);// 第 j 位右边的数的所有可能性(000 到 999)
			int l = n / p / 10;// 第 j 位左边的整数 (abc)
			int r = n % p;// 第 j 位右边的整数(efg)
			int dj = n / p % 10;// dj 是第 j 位上的数字

//			j 位左边整数取 (000 到 abc-1)的方案
			if (i != 0)
				res += l * p;
//			如果 i 是 0 (即第j位上是 i(0) 的情况,第j位左边的整数必须大于  0 ,不然 j 位上的 0 就消去了)
			if (i == 0 && l != 0)// 001 到 abc-1
				res += (l - 1) * p;

//			j 位左边整数取 abc(最大)的方案【 (i != 0 || l != 0)】判断高位不为 0 
//			此处是第 j 位取 i 的情况
			if ((dj > i) && (i != 0 || l != 0))// 高位 dj > i ,右边任取,p 种可能
				res += p;
			if ((dj == i) && (i != 0 || l != 0))// 高位 dj == i,右边只有 r+1 种可能
				res += r + 1;
		}
		return res;
	}

	public static void main(String[] args)
	{
		int a, b;
		Scanner sc = new Scanner(System.in);
		while (true)
		{
			a = sc.nextInt();
			b = sc.nextInt();
			if (a == 0 && b == 0)
				break;

			if (a > b)
			{
				int tmp = a;
				a = b;
				b = tmp;
			}

			for (int i = 0; i <= 9; i++)
			{
				//前缀和
				System.out.print(cnt(b, i) - cnt(a - 1, i) + " ");
			}
			System.out.println();
		}

	}

二、贪心算法

1. 区间问题

(1)区间选点

🥞 参考传送门

🥞 Java中的Lambda表达式的使用

	static int N = 100010;
	static Range[] range = new Range[N];

//	区间类
	static class Range
	{
		int l;
		int r;

		public Range(int l, int r)
		{
			this.l = l;
			this.r = r;
		}
	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		for (int i = 0; i < n; i++)
			range[i] = new Range(sc.nextInt(), sc.nextInt());
//		排序需指定数组的范围,因为数组开大了有 空数据
		Arrays.sort(range, 0, n, (o1, o2) -> o1.r - o2.r);// 按照右端点升序排序
		int res = 0;// 当前点的数量
		int end = Integer.MIN_VALUE;// 记录上一个点的位置
		for (int i = 0; i < n; i++)// 遍历每一个区间
		{
			if (range[i].l > end)// 说明此区间没被上一个选中的区间覆盖
			{
				res++;
				end = range[i].r;
			}
		}
		System.out.println(res);
	}

(2)最大不相交区间数

🐷 参考链接
🐷 使用 集合类 的方法排序时切记排除空数据

	static int n;
	static int N = (int) 1e5 + 10;
	static Range[] a = new Range[N];

//	区间类
	static class Range
	{
		int l;
		int r;

		public Range(int l, int r)
		{
			super();
			this.l = l;
			this.r = r;
		}

	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		for (int i = 0; i < n; i++)
		{
			a[i] = new Range(sc.nextInt(), sc.nextInt());
		}
//		Arrays.sort排序,切记要排除空数据
		Arrays.sort(a, 0, n, (o1, o2) -> o1.r - o2.r);//所有区间按右边界从小到大排序

		int res = 0;
		int e = (int) -2e9;
		for (int i = 0; i < n; i++)
		{
			if (e < a[i].l)// 与前边的区间不重合
			{
				res++;
				e = a[i].r;
			}
		}
		System.out.println(res);
	}

(3)区间分组

🤠 参考链接

	static int N = 100010;
	static int n;
	static Node[] range = new Node[N];

//	区间类
	static class Node
	{
		int l;
		int r;
		
		public Node(int l, int r)
		{
			this.l = l;
			this.r = r;
		}
	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		for (int i = 0; i < n; i++)
		{
			int a = sc.nextInt();
			int b = sc.nextInt();
			range[i] = new Node(a, b);
		}
//		全局数组开足了,所以排序或者初始化 得指定 范围
		Arrays.sort(range, 0, n, (o1, o2) -> o1.l - o2.l);
//		小根堆(优先队列) 维护所有区间组中的右端点
		PriorityQueue<Integer> minheap = new PriorityQueue<>();

		for (int i = 0; i < n; i++)
		{
//			第一个区间 || 此区间左端小于 所有区间组右端点的最小值,即加入即重合
			if (minheap.isEmpty() || minheap.peek() >= range[i].l)
			{
				minheap.add(range[i].r);// 小根堆新加入一个右端点,又是一个新的区间组
			} else
			{
//				默认策略:加入右端点最小的区间组
				minheap.poll();// 删除对头(根)并返回 值
				minheap.add(range[i].r);
			}
		}
//		小根堆维护的右端点数就是 区间组的数量
		System.out.println(minheap.size());
	}

(4)区间覆盖

🤠 参考链接

	static int N = (int) 1e5;// 2*10的5次方
	static int n, s, t;
	static Range[] ranges = new Range[N];

//	区间类
	static class Range
	{
		int l;
		int r;

		public Range(int l, int r)
		{
			this.l = l;
			this.r = r;
		}
	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		s = sc.nextInt();// start:要覆盖区间的左端点
		t = sc.nextInt();// tail:要覆盖区间的右端点
		n = sc.nextInt();// n 个区间
		for (int i = 0; i < n; i++)
		{
			ranges[i] = new Range(sc.nextInt(), sc.nextInt());
		}
//		排序记得加范围,按左端点由小到大排序
		Arrays.sort(ranges, 0, n, (o1, o2) -> o1.l - o2.l);

		int res = 0;
		boolean flag = false;
		for (int i = 0; i < n; i++) // 枚举排序好的区间
		{
			int j = i;
			int r = (int) -2e9;
//			选出左端点包含 start, 右端点最大的区间
			while (j < n && ranges[j].l <= s)
			{
				r = Math.max(r, ranges[j].r);
				j++;
			}
//			小于 start 的区间中的 最大右端点都小于 start :无解
			if (r < s)
			{
				res = -1;
				break;
			}
			res++; // 经上没 break 选择区间 res+1
//			右端点已经包含 待覆盖区间的右端点 tail 了,成功
			if (r >= t)  
			{
				flag = true;
				break;
			}
//			i 移到 j-1 的位置(此时的j并没有枚举到,只是不符合上次的start条见跳出来循环)
			i = j - 1; // 由于待会 i 会 ++
			s = r;// 新的起点赋值为最大右端点
		}
		if (flag)
			System.out.println(res);
		else
		{
			System.out.println(-1);
		}
	}

2. Huffman 树

(1)合并果子

🤠 原题地址

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
//		优先队列维护小根堆
		PriorityQueue<Integer> heap = new PriorityQueue<>();

		for (int i = 0; i < n; i++)
		{
			int a = sc.nextInt();
			heap.add(a);
		}

		int res = 0;
//		当小根堆只剩下一个元素的时候,就是求得结果了
		while (heap.size() > 1)
		{
			int a = heap.poll();
			int b = heap.poll();
//			每次合并两个最小的节点
			res += a + b;
			heap.add(a + b);
		}
		System.out.println(res);
	}

3. 排序不等式

(1)排队打水

参考地址

	static int N = 100010;
	static int[] t = new int[N];

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		for (int i = 0; i < n; i++)
		{
			t[i] = sc.nextInt();
		}
		// 用 long 接受结果,防止爆 int
		long res = 0;
//		全局的数组排序时切记要 指定范围
		Arrays.sort(t, 0, n);
		for (int i = 0; i < n; i++)
		{
			res += t[i] * (n - i - 1);
		}
		System.out.println(res);
	}

4. 绝对值不等式

(1)货仓选址

原题地址
✨ 中位数

import java.util.*;

class Main{
    static int N = 100010;
    static int[] a = new int[N];
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        
        for(int i = 0; i < n; i++ )
        {
            a[i] = sc.nextInt();
        }
        
        Arrays.sort(a,0,n);
        int res = 0;
        for(int i = 0; i < n; i++)
        {
            // 两点间 找一个点 x 距离两点的距离之和 最小, x必须在 两点之间 取得最小值 为 两点的距离
            // 分左右两组点,只要满足 x 在每一对点之间就取得最小值 (中位数/ 两中位数中的任1个)
            res += Math.abs(a[i]-a[n>>1]); 
        }
        System.out.print(res);
        
    } 
}

5. 推公式

(1)耍杂技的牛

原题地址
参考题解


import java.util.*;

public class Main
{
	static int N = 50010;
	static cow[] a = new cow[N];

	static class cow
	{
		int w;// 重量
		int s;// 强壮度

		public cow(int w, int s)
		{
			this.w = w;
			this.s = s;
		}

	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		for (int i = 0; i < n; i++)
		{
			a[i] = new cow(sc.nextInt(), sc.nextInt());
		}
//		按 w+s 从小到大排序
		Arrays.sort(a, 0, n, (o1, o2) -> (o1.w + o1.s) - (o2.w + o2.s));
		int sum = 0;
		int res = -0x3f3f3f3f;
//		牛是从上到下枚举的,上小下大
		for (int i = 0; i < n; i++)
		{
//第i个牛只需承担 i-1 个牛的重量(排除本身重量),所以是先算风险值,再算下边的重量相加
			res = Math.max(res, sum - a[i].s);
			sum += a[i].w;
		}
		System.out.println(res);
	}
}

posted @ 2023-02-02 23:20  兑生  阅读(13)  评论(0编辑  收藏  举报  来源
Live2D