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.

192 lines
5.7 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 class_2022_11_4_week;
import java.util.Arrays;
// 来自第四届全国大学生算法设计与编程挑战赛(秋季赛)
// 给定两个长度为N的数组a[]和b[]
// 也就是对于每个位置i来说有a[i]和b[i]两个属性
// i a[i] b[i]
// j a[j] b[j]
// 现在想为了i选一个最好的j位置搭配能得到最小的如下值:
// (a[i] + a[j]) ^ 2 + b[i] + b[j]
// 我们把这个最小的值定义为i的最in值
// 比如 :
// a = { 2, 3, 6, 5, 1 }
// b = { 100, 70, 20, 40, 150 }
// 0 1 2 3 4
// 0位置和2位置搭配可以得到最in值 : 184
// 1位置和2位置搭配可以得到最in值 : 171
// 2位置和1位置搭配可以得到最in值 : 171
// 3位置和1位置搭配可以得到最in值 : 174
// 4位置和2位置搭配可以得到最in值 : 219
// 注意 : i位置可以和i位置(自己)搭配并不是说i和j一定要是不同的位置
// 返回每个位置i的最in值
// 比如上面的例子,最后返回[184, 171, 171, 174, 219]
// 1 <= N <= 10^5
// 1 <= a[i]、b[i] <= 10^9
public class Code05_PickBestToGetInValue {
// 暴力方法
// 时间复杂度O(N^2)
// 为了测试
public static long[] inValues1(int[] a, int[] b) {
int n = a.length;
long[] ans = new long[n];
for (int i = 0; i < n; i++) {
long curAns = Long.MAX_VALUE;
for (int j = 0; j < n; j++) {
long cur = (long) (a[i] + a[j]) * (a[i] + a[j]) + b[i] + b[j];
curAns = Math.min(curAns, cur);
}
ans[i] = curAns;
}
return ans;
}
// 正式方法
// 时间复杂度O(N*logN)
// (a[i] + a[j]) ^ 2 + b[i] + b[j]
// a[i]^2 + b[i] + 2a[i]a[j] + a[j]^2 + b[j]
// a[i] * ( a[i] + b[i]/a[i] + 2a[j] + (a[j]^2 + b[j])/a[i])
// 令S(j) = 2a[j]
// 令T(j) = a[j]^2 + b[j]
// 那么对于i来说就是选择j让下面得到最小值
// a[i] * ( a[i] + b[i]/a[i] + S(j) + T(j)/a[i])
// 选择最小的S(j) + T(j)/a[i],就得到了答案
// 剩下的一切都是围绕这个
public static long[] inValues2(int[] a, int[] b) {
int n = a.length;
// i a[i] b[i]
// i s[i] t[i]
long[][] st = new long[n][2];
for (int i = 0; i < n; i++) {
st[i][0] = 2L * a[i];
st[i][1] = (long) a[i] * a[i] + b[i];
}
// 只需要根据S值从大到小排序即可
// 下面的比较器定义稍复杂因为java里排序会严格检查传递性
// 所以策略参考了S和T其实只需要根据S值从大到小排序即可
Arrays.sort(st, (x, y) -> x[0] != y[0] ? (x[0] > y[0] ? -1 : 1) : (x[1] <= y[1] ? -1 : 1));
int[] stack = new int[n];
int r = 0;
for (int i = 0; i < n; i++) {
// s 大 -> 小
long s = st[i][0];
long t = st[i][1];
while (r > 0
&& tail(st, stack, r) >= // 栈顶,和栈顶再下一个 ai大到什么程度栈顶更好!
// 当前记录当ai大到什么程度比栈顶好
better(st[stack[r - 1]][0], st[stack[r - 1]][1], s, t)) {
r--;
}
stack[r++] = i;
}
int[][] arr = new int[n][3];
for (int i = 0; i < n; i++) {
arr[i][0] = i;
arr[i][1] = a[i];
arr[i][2] = b[i];
}
// 只需要根据a值从大到小排序即可
// 下面的比较器定义稍复杂排序策略参考了a和位置i原因是:
// 因为java的系统排序会检查传递性
// 所以只用a排序会导致java内部认为这个排序是无效的
// 其实只需要根据a值从大到小排序即可
Arrays.sort(arr, (x, y) -> x[1] != y[1] ? (x[1] < y[1] ? 1 : -1) : (x[0] - y[0]));
long[] ans = new long[n];
for (int k = 0; k < n; k++) {
int i = arr[k][0];
int ai = arr[k][1];
int bi = arr[k][2];
while (tail(st, stack, r) > ai) {
r--;
}
long sj = st[stack[r - 1]][0];
long tj = st[stack[r - 1]][1];
// a[i] * ( a[i] + b[i]/a[i] + S(j) + T(j)/a[i])
long curAns = sj * ai + tj + (long) ai * ai + bi;
ans[i] = curAns;
}
return ans;
}
public static int tail(long[][] st, int[] deque, int r) {
if (r == 1) {
return 1;
}
return better(st[deque[r - 2]][0], st[deque[r - 2]][1], st[deque[r - 1]][0], st[deque[r - 1]][1]);
}
// 入参时候s1>=s2这是一定的
// 返回当ai大到什么值的时候(s2+t2/ai) <= (s1+t1/ai)
// 即 : ai大到什么值的时候后者更好
public static int better(long s1, long t1, long s2, long t2) {
if (s1 == s2) {
return t1 <= t2 ? Integer.MAX_VALUE : 1;
}
// s1 > s2
if (t1 >= t2) {
return 1;
}
// s1 > s2
// t1 < t2
long td = t2 - t1;
long sd = s1 - s2;
return (int) ((td + sd - 1) / sd);
}
// 为了测试
public static int[] randomArray(int n, int v) {
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
ans[i] = (int) (Math.random() * v) + 1;
}
return ans;
}
// 为了测试
public static boolean isSameArray(long[] arr1, long[] arr2) {
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// 为了测试
public static void main(String[] args) {
int N = 100;
int A = 100;
int B = 50000;
int testTime = 50000;
System.out.println("功能测试开始");
for (int i = 0; i < testTime; i++) {
int n = (int) (Math.random() * N) + 1;
int[] a = randomArray(n, A);
int[] b = randomArray(n, B);
long[] ans1 = inValues1(a, b);
long[] ans2 = inValues2(a, b);
if (!isSameArray(ans1, ans2)) {
System.out.println("出错了!");
}
}
System.out.println("功能测试结束");
System.out.println("性能测试开始");
int n = 100000;
int v = 1000000000;
int[] a = randomArray(n, v);
int[] b = randomArray(n, v);
System.out.println("数组长度 : " + n);
System.out.println("数值范围 : " + v);
long start = System.currentTimeMillis();
inValues2(a, b);
long end = System.currentTimeMillis();
System.out.println("运行时间 : " + (end - start) + " 毫秒");
System.out.println("性能测试结束");
}
}