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

2 years ago
package class01;
import java.util.Arrays;
/*
* xhpN
* xx[i]ix
* hphp[i]i
* 便xhp
* 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) + " 毫秒");
}
}