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.

150 lines
4.3 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 class47;
import java.util.Arrays;
import java.util.HashMap;
// 需要证明:
// 一个集合中假设整体的累加和为K
// 不管该集合用了什么样的0集合划分方案当一个新的数到来时
// 1) 如果该数是-K那么任何0集合的划分方案中因为新数字的加入0集合的数量都会+1
// 2) 如果该数不是-K那么任何0集合的划分方案中0集合的数量都会不变
public class Problem_0465_OptimalAccountBalancing {
// 用位信息替代集合结构的暴力尝试
public static int minTransfers1(int[][] transactions) {
// 不管转账有几笔最终每个人收到的钱如果是0不进入debt数组
// 只有最终收到的钱不等于0的人进入debt数组
// 收到的钱4说明该给出去
// 收到的钱,-4说明该要回来
// debt数组的累加和必为0
int[] debt = debts(transactions);
int N = debt.length;
return N - process1(debt, (1 << N) - 1, 0, N);
}
// set -> int -> 不使用值 -> 只使用状态!
// 001110 0号人不在集合里1、2、3号人在集合里4、5号人不在集合里
// sum -> set这个集合累加和是多少sum被set决定的
// debt数组收到的钱的数组(固定)
// N, debt的长度(固定)
// 返回值含义 : set这个集合中最多能划分出多少个小集合累加和是0返回累加和是0的小集合最多的数量
public static int process1(int[] debt, int set, int sum, int N) {
// set中只有一个人的时候
// debt中没有0的所以每个人一定都需要转账
if ((set & (set - 1)) == 0) {
return 0;
}
int value = 0;
int max = 0;
// 尝试,每一个人都最后考虑
// 0,如果最后考虑
// 1,如果最后考虑
// 2,如果最后考虑
// ....
// n-1最后考虑
// 011001
// 0
// 1不能考虑因为不在集合里
for (int i = 0; i < N; i++) {
value = debt[i];
if ((set & (1 << i)) != 0) { // i号人真的在集合里才能考虑
// 011001
// 3号人在
// 3号人之前010001考虑0号人和4号人剩下的情况
// process ( set ^ (1 << i) , sum - value )
max = Math.max(max, process1(debt, set ^ (1 << i), sum - value, N));
}
}
return sum == 0 ? max + 1 : max;
}
// 上面的尝试过程 + 记忆化搜索
// 最优解
public static int minTransfers2(int[][] transactions) {
int[] debt = debts(transactions);
int N = debt.length;
int sum = 0;
for (int num : debt) {
sum += num;
}
int[] dp = new int[1 << N];
Arrays.fill(dp, -1);
return N - process2(debt, (1 << N) - 1, sum, N, dp);
}
public static int process2(int[] debt, int set, int sum, int N, int[] dp) {
if (dp[set] != -1) {
return dp[set];
}
int ans = 0;
if ((set & (set - 1)) != 0) {
int value = 0;
int max = 0;
for (int i = 0; i < N; i++) {
value = debt[i];
if ((set & (1 << i)) != 0) {
max = Math.max(max, process2(debt, set ^ (1 << i), sum - value, N, dp));
}
}
ans = sum == 0 ? max + 1 : max;
}
dp[set] = ans;
return ans;
}
public static int[] debts(int[][] transactions) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int[] tran : transactions) {
map.put(tran[0], map.getOrDefault(tran[0], 0) + tran[2]);
map.put(tran[1], map.getOrDefault(tran[1], 0) - tran[2]);
}
int N = 0;
for (int value : map.values()) {
if (value != 0) {
N++;
}
}
int[] debt = new int[N];
int index = 0;
for (int value : map.values()) {
if (value != 0) {
debt[index++] = value;
}
}
return debt;
}
// 为了测试
public static int[][] randomTrans(int s, int n, int m) {
int[][] trans = new int[s][3];
for (int i = 0; i < s; i++) {
trans[i][0] = (int) (Math.random() * n);
trans[i][1] = (int) (Math.random() * n);
trans[i][2] = (int) (Math.random() * m) + 1;
}
return trans;
}
// 为了测试
public static void main(String[] args) {
int s = 8;
int n = 8;
int m = 10;
int testTime = 10000;
System.out.println("测试开始");
for (int i = 0; i < testTime; i++) {
int[][] trans = randomTrans(s, n, m);
int ans1 = minTransfers1(trans);
int ans2 = minTransfers2(trans);
if (ans1 != ans2) {
System.out.println("Oops!");
System.out.println(ans1);
System.out.println(ans2);
break;
}
}
System.out.println("测试结束");
}
}