You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
8.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package class01;
import java.util.Arrays;
/*
* 给定两个数组x和hp长度都是N。
* x数组一定是有序的x[i]表示i号怪兽在x轴上的位置
* hp数组不要求有序hp[i]表示i号怪兽的血量
* 为了方便起见可以认为x数组和hp数组中没有负数。
* 再给定一个正数range表示如果法师释放技能的范围长度(直径!)
* 被打到的每只怪兽损失1点血量。
* 返回要把所有怪兽血量清空至少需要释放多少次aoe技能
* 三个参数int[] x, int[] hp, int range
* 返回int 次数
* */
public class Code06_AOE {
// 纯暴力解法
// 太容易超时
// 只能小样本量使用
public static int minAoe1(int[] x, int[] hp, int range) {
boolean allClear = true;
for (int i = 0; i < hp.length; i++) {
if (hp[i] > 0) {
allClear = false;
break;
}
}
if (allClear) {
return 0;
} else {
int ans = Integer.MAX_VALUE;
for (int left = 0; left < x.length; left++) {
if (hasHp(x, hp, left, range)) {
minusOneHp(x, hp, left, range);
ans = Math.min(ans, 1 + minAoe1(x, hp, range));
addOneHp(x, hp, left, range);
}
}
return ans;
}
}
public static boolean hasHp(int[] x, int[] hp, int left, int range) {
for (int index = left; index < x.length && x[index] - x[left] <= range; index++) {
if (hp[index] > 0) {
return true;
}
}
return false;
}
public static void minusOneHp(int[] x, int[] hp, int left, int range) {
for (int index = left; index < x.length && x[index] - x[left] <= range; index++) {
hp[index]--;
}
}
public static void addOneHp(int[] x, int[] hp, int left, int range) {
for (int index = left; index < x.length && x[index] - x[left] <= range; index++) {
hp[index]++;
}
}
// 为了验证
// 不用线段树,但是贪心的思路,和课上一样
// 1) 总是用技能的最左边缘刮死当前最左侧的没死的怪物
// 2) 然后向右找下一个没死的怪物重复步骤1)
public static int minAoe2(int[] x, int[] hp, int range) {
// 举个例子:
// 如果怪兽情况如下,
// 怪兽所在x数组 : 2 3 5 6 7 9
// 怪兽血量hp数组 : 2 4 1 2 3 1
// 怪兽编号 : 0 1 2 3 4 5
// 技能直径range = 2
int n = x.length;
int[] cover = new int[n];
// 首先求cover数组
// 如果技能左边界就在0号怪兽那么技能到2号怪兽就覆盖不到了
// 所以cover[0] = 2;
// 如果技能左边界就在1号怪兽那么技能到3号怪兽就覆盖不到了
// 所以cover[1] = 3;
// 如果技能左边界就在2号怪兽那么技能到5号怪兽就覆盖不到了
// 所以cover[2] = 5;
// 如果技能左边界就在3号怪兽那么技能到5号怪兽就覆盖不到了
// 所以cover[3] = 5;
// 如果技能左边界就在4号怪兽那么技能到6号怪兽越界位置就覆盖不到了
// 所以cover[4] = 6(越界位置);
// 如果技能左边界就在5号怪兽那么技能到6号怪兽越界位置就覆盖不到了
// 所以cover[5] = 6(越界位置);
// 综上:
// 如果怪兽情况如下,
// 怪兽所在x数组 : 2 3 5 6 7 9
// 怪兽血量hp数组 : 2 4 1 2 3 1
// 怪兽编号 : 0 1 2 3 4 5
// cover数组情况 : 2 3 5 5 6 6
// 技能直径range = 2
// cover[i] = j表示如果技能左边界在i怪兽那么技能会影响i...j-1号所有的怪兽
// 就是如下的for循环在求cover数组
int r = 0;
for (int i = 0; i < n; i++) {
while (r < n && x[r] - x[i] <= range) {
r++;
}
cover[i] = r;
}
int ans = 0;
for (int i = 0; i < n; i++) {
// 假设来到i号怪兽了
// 如果i号怪兽的血量>0说明i号怪兽没死
// 根据我们课上讲的贪心:
// 我们要让技能的左边界刮死当前的i号怪兽
// 这样能够让技能尽可能的往右释放,去尽可能的打掉右侧的怪兽
// 此时cover[i],正好的告诉我们,技能影响多大范围。
// 比如当前来到100号怪兽血量30
// 假设cover[100] == 200
// 说明技能左边界在100位置可以影响100号到199号怪兽的血量。
// 为了打死100号怪兽我们释放技能30次
// 释放的时候100号到199号怪兽都掉血30点
// 然后继续向右寻找没死的怪兽,像课上讲的一样
if (hp[i] > 0) {
int minus = hp[i];
for (int index = i; index < cover[i]; index++) {
hp[index] -= minus;
}
ans += minus;
}
}
return ans;
}
// 正式方法
// 关键点就是:
// 1) 线段树
// 2) 总是用技能的最左边缘刮死当前最左侧的没死的怪物
// 3) 然后向右找下一个没死的怪物重复步骤2)
public static int minAoe3(int[] x, int[] hp, int range) {
int n = x.length;
int[] cover = new int[n];
int r = 0;
// cover[i] : 如果i位置是技能的最左侧技能往右的range范围内最右影响到哪
for (int i = 0; i < n; i++) {
while (r < n && x[r] - x[i] <= range) {
r++;
}
cover[i] = r - 1;
}
SegmentTree st = new SegmentTree(hp);
st.build(1, n, 1);
int ans = 0;
for (int i = 1; i <= n; i++) {
int leftHP = st.query(i, i, 1, n, 1);
if (leftHP > 0) {
ans += leftHP;
st.add(i, cover[i - 1] + 1, -leftHP, 1, n, 1);
}
}
return ans;
}
public static class SegmentTree {
private int MAXN;
private int[] arr;
private int[] sum;
private int[] lazy;
public SegmentTree(int[] origin) {
MAXN = origin.length + 1;
arr = new int[MAXN];
for (int i = 1; i < MAXN; i++) {
arr[i] = origin[i - 1];
}
sum = new int[MAXN << 2];
lazy = new int[MAXN << 2];
}
private void pushUp(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
private void pushDown(int rt, int ln, int rn) {
if (lazy[rt] != 0) {
lazy[rt << 1] += lazy[rt];
sum[rt << 1] += lazy[rt] * ln;
lazy[rt << 1 | 1] += lazy[rt];
sum[rt << 1 | 1] += lazy[rt] * rn;
lazy[rt] = 0;
}
}
public void build(int l, int r, int rt) {
if (l == r) {
sum[rt] = arr[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
pushUp(rt);
}
public void add(int L, int R, int C, int l, int r, int rt) {
if (L <= l && r <= R) {
sum[rt] += C * (r - l + 1);
lazy[rt] += C;
return;
}
int mid = (l + r) >> 1;
pushDown(rt, mid - l + 1, r - mid);
if (L <= mid) {
add(L, R, C, l, mid, rt << 1);
}
if (R > mid) {
add(L, R, C, mid + 1, r, rt << 1 | 1);
}
pushUp(rt);
}
public int query(int L, int R, int l, int r, int rt) {
if (L <= l && r <= R) {
return sum[rt];
}
int mid = (l + r) >> 1;
pushDown(rt, mid - l + 1, r - mid);
int ans = 0;
if (L <= mid) {
ans += query(L, R, l, mid, rt << 1);
}
if (R > mid) {
ans += query(L, R, mid + 1, r, rt << 1 | 1);
}
return ans;
}
}
// 为了测试
public static int[] randomArray(int n, int valueMax) {
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
ans[i] = (int) (Math.random() * valueMax) + 1;
}
return ans;
}
// 为了测试
public static int[] copyArray(int[] arr) {
int N = arr.length;
int[] ans = new int[N];
for (int i = 0; i < N; i++) {
ans[i] = arr[i];
}
return ans;
}
// 为了测试
public static void main(String[] args) {
int N = 50;
int X = 500;
int H = 60;
int R = 10;
int testTime = 50000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int len = (int) (Math.random() * N) + 1;
int[] x2 = randomArray(len, X);
Arrays.sort(x2);
int[] hp2 = randomArray(len, H);
int[] x3 = copyArray(x2);
int[] hp3 = copyArray(hp2);
int range = (int) (Math.random() * R) + 1;
int ans2 = minAoe2(x2, hp2, range);
int ans3 = minAoe3(x3, hp3, range);
if (ans2 != ans3) {
System.out.println("出错了!");
}
}
System.out.println("测试结束");
N = 500000;
long start;
long end;
int[] x2 = randomArray(N, N);
Arrays.sort(x2);
int[] hp2 = new int[N];
for (int i = 0; i < N; i++) {
hp2[i] = i * 5 + 10;
}
int[] x3 = copyArray(x2);
int[] hp3 = copyArray(hp2);
int range = 10000;
start = System.currentTimeMillis();
System.out.println(minAoe2(x2, hp2, range));
end = System.currentTimeMillis();
System.out.println("运行时间 : " + (end - start) + " 毫秒");
start = System.currentTimeMillis();
System.out.println(minAoe3(x3, hp3, range));
end = System.currentTimeMillis();
System.out.println("运行时间 : " + (end - start) + " 毫秒");
}
}