动态规划+贪心
背包问题、线性DP、区间DP、计数类DP、数位统计DP、状态压缩DP、树形DP、记忆化搜索、贪心
文章目录
一、动态规划
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)区间选点
🥞 参考传送门
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);
}
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)