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

2 years ago
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("测试结束");
}
}