算法模板【自用】

116 阅读5分钟

0x00 语言基础

Java 快速输入

public class Main {
    public static void main(String[] args) throw IOException {
        int a = nextInt();
        double b = nextDouble();
        
        System.out.println(a);
        System.out.println(b);
    }
    
    // 快速输入
    static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    static StringTokenizer tokenizer = new StringTokenizer("");
​
    static String next() throws IOException {
        while (!tokenizer.hasMoreTokens()) {
            tokenizer = new StringTokenizer(reader.readLine());
        }
        return tokenizer.nextToken();
    }
    
    static int nextInt() throws IOException {
        return Integer.parseInt(next());
    }
}

0x01 算法基础

二分

# 求非降序范围[l, r) 找下界
public int binarySearch(int[] nums, target) {
    int l = 0, r = nums.length - 1;
    while (l < r) {
        int mid = (r - l >> 1) + l; // 防溢出
        if (nums[mid] < target) l = mid + 1;
        else r = mid;
        // 进阶用法
        // int sum = check(mid);
        // if (sum < target) l = mid + 1;
        // else r = mid;
    }
    return nums[l] == target ? l : -1 ;
}

0x02 搜索

1 DFS

① 集合划分问题

讲集合划分为n个大小相等的子集

int[] arr;  // 数组
int[] cur;  // 划分子集
int ave;    // 平均值
int k;      // k等分
​
public boolean divide(int[] arr, int k) {
    this.arr = arr;
    this.k = k;
    
    // 判断是否可以分为k等分
    int sum = Arrays.stream(arr).sum();
    ave = sum / k;
    if (ave * k != sum) return false;
    
    // 排序
    Arrays.sort(arr);
    cur = new int[k];
    return dfs(arr.length - 1);
}
​
private boolean dfs(int idx) {
    if (idx == -1) return true;
    out:for (int i = 0; i < k; i++) {
        for (int j = 0; j < i; j++) {
            if (cur[j] == cur[i]) continue out;
        }
        int u = arr[idx];
        if (cur[i] + u > ave) continue;
        cur[i] += u;
        if (dfs(idx - 1, cur)) return true;
        cur[i] -= u;
    }
    return false;
}

0x03 动态规划

1 背包问题

// 01背包
public void ZeroOnePack(int v, int w) {
    for (int i = 1; i <= N; i++) {
        for (int j = V; j >= v; j--) {
            dp[j] = Math.max(dp[j], dp[j - v] + w);
        }
    }
}
​
// 完全背包
public void CompletePack(int v, int w) {
    for (int i = 1; i <= N; i++) {
        for (int j = v; j <= V; j++) {
            dp[j] = Math.max(dp[j], dp[j - v] + w);
        }
    } 
}
// 多重背包
public void MultiPack(int v, int w, int s) {
    if (s * v >= V) CompletePack(v, w);
    for (int k = 1; k < s; s-= k, k <<= 1) ZeroOnePack(v * k, w * k)
    ZeroOnePack(v * s, w * s)
}
​
# 混合背包
for i in range(N):
    v, w, s = map(int, input().split())
    if s == -1:
        ZeroOnePack(v, w)
    elif s == 0:
        CompletePack(v, w)
    else:
        MultiPack(v, w, s)
# 二维费用背包
N, V, M = map(int, input().split())
f = [[0] * (M + 1) for i in range(V + 1)]
​
for i in range(N):
    v, m, w = map(int, input().split())
    for j in range(V, v - 1, -1):
        for k in range(M, m - 1, -1):
            f[j][k] = max(f[j][k], f[j - v][k - m] + w)
print(f[V][M])

2 最长XX子序列问题

// 最长公共子序列(LCS)
public int LCS(String text1, String text2) {
    char[] s1 = text1.toCharArray();
    char[] s2 = text2.toCharArray();
    int len1 = s1.length, len2 = s2.length;

    int[][] dp = new int[len1 + 1][len2 + 1];
    for (int i = 1; i <= len1; i++) {
        for (int j = 1; j <= len2; j++) {
            if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
            else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
        }
    }
    return dp[len1][len2];
}
// 最长上升子序列
public int LIS(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    dp[0] = 1;
    int maxv = 1;
    for (int i = 1; i < n; i++) {
        dp[i] = 1;
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) dp[i] = Math.max(dp[i], dp[j] + 1);
        }
        maxv = Math.max(maxv, dp[i]);
    }

    return maxv;
}
// 最长公共上升子序列
public int LCIS(int[] a, int[] b) {
    int n = a.length, m = a.length;
    int[][] f = new int[n + 1][m + 1];

    for (int i = 1; i <= n; i++) {
        int maxv = 1;
        for (int j = 1; j <= m; j++) {
            f[i][j] = f[i - 1][j];
            if (b[j - 1] == a[i - 1]) f[i][j] = Math.max(f[i][j], maxv);
            if (b[j - 1] < a[i - 1]) maxv = Math.max(f[i - 1][j] + 1, maxv);
        }
    }
    int res = 0;
    for (int i = 1; i <= m; i++) res = Math.max(res, f[n][i]);
    return res;
}

3 线性DP

// 最短编辑距离,word1 到 word2需要变化多少次?
// 1. word1末尾增加一位
// 2. word2末尾增加一位  -- 等价于 word1末尾删除一位
// 3. word1末尾修改一位  -- 等价于 word2末尾修改一位
public int minDistance(String word1, String word2) {
    char[] s1 = word1.toCharArray();
    char[] s2 = word2.toCharArray();
    int n = s1.length;
    int m = s2.length;
    
    // dp[i][j]表示,s1[1...i]到s2[1...j]最短编辑距离
    int[][] dp = new int[n + 1][m + 1];
    
    // 初始化,空字符串到长度位i的字符串需要操作i次
    for (int i = 0; i <= n; i++) dp[i][0] = i;
    for (int i = 0; i <= m; i++) dp[0][i] = i;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            // s1[i] == s2[j] 不需要操作,继承dp[i - 1][j - 1]的情况即可
            if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
            // s1[i] != s2[j]
            // 三种情况:
            // 1. s1 增加1位 dp[i - 1][j] + 1
            // 2. s2 增加1位 dp[i][j - 1] + 1
            // 3. s1 修改最后一位 dp[i - 1][j - 1] + 1
            else dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
        }
    }
    return dp[n][m];
}
// 最短编辑距离变形(只能修改word1,且不需要word1 == word2,word1包含word2即可)
public int minDistance(String word1, String word2) {
    char[] s1 = word1.toCharArray();
    char[] s2 = word2.toCharArray();
    int n = s1.length;
    int m = s2.length;
    
    // dp[i][j]表示,s1[1...i]到s2[1...j]最短编辑距离
    int[][] dp = new int[n + 1][m + 1];
    
    // 初始化,s2为空字符串则不需要操作也包含
    for (int i = 0; i <= n; i++) dp[i][0] = 0;
    // 初始化,s1为空字符串则无法通过修改s1达成目标
    for (int i = 0; i <= m; i++) dp[0][i] = INF;
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            // s1[i] == s2[j] 不需要操作,继承dp[i - 1][j - 1]的情况即可
            if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
            // s1[i] != s2[j]
            // 只剩2种情况: 
            // 1. s1 修改最后一位 dp[i - 1][j - 1] + 1
            // 2. 或者当位置不参与考虑,继承上一位
            else dp[i][j] = Math.min(dp[i - 1][j - 1] + 1, dp[i - 1][j]);
        }
    }
    return dp[n][m];
}

4 区间DP

棋盘分割简化题

public long sellingWood(int m, int n, int[][] prices) {
    long[][] dp = new long[m + 1][n + 1];
    for (int[] price : prices) {
        int h = price[0], w = price[1], v = price[2];
        dp[h][w] = v;
    }

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            for (int k = 1; k < i; k++)
                dp[i][j] = Math.max(dp[i][j], dp[k][j] + dp[i - k][j]);
            
            for (int k = 1; k < j; k++)
                dp[i][j] = Math.max(dp[i][j], dp[i][k] + dp[i][j - k]);
        }
    }
    return dp[m][n];
}

5 状压DP

// 状态压缩DP(区间分割问题,nums平均分成k份)
public boolean divide(int[] nums, int k) {
    // 预处理
    int sum = Arrays.stream(nums).sum();
    if (sum % k != 0) return false;

    // 排序
    Arrays.sort(nums);
    int n = nums.length;
    // 状态压缩,二进制表示物品选择情况,类似位图
    int size = 1 << n;
    int target = sum / k;
    if (nums[nums.length - 1] > target) return false;

    boolean[] dp = new boolean[size];
    int[] curSum = new int[size];
    // DP初始化
    dp[0] = true;
    // 枚举二进制压缩后的选择情况
    for (int i = 0; i < size; i++) {
        // 只处理被更新过的
        if (!dp[i]) continue;
        // 枚举
        for (int j = 0; j < n; j++) {
            // 当前物品已经被选择了
            if ((i & (1 << j)) != 0) continue;
            // 接下来要考虑的选择状态
            int next = i | (1 << j);
            // 如果选择后超出target,说明无法被放到当前容器
            // 因为排序过,后面的都比他大,只能比target更大,直接break
            if (curSum[i] % target + nums[j] > target) break;
            // 更新dp状态
            dp[next] = true;
            // 更新数据
            curSum[next] = curSum[i] + nums[j];
        }
    }

    return dp[size - 1];
}
// 状态压缩DP(区间分割问题,nums分成k份,每一份去最大值,再取这些最大值中的的最小值)
public int divide(int[] nums, int k) {
    int n = nums.length;
    int[] sum = new int[1 << n];	// sun[i] 表示,数据选择情况为i时,和为sum[i]
    // 求sum[i], sum[i] = sum[i - (1 << j)] + nums[j],递推求和
    for (int i = 1; i < (1 << n); i++) {
		for (int j = 0; j < n; j++) {
            if ((i & (1 << j)) == 0) continue;
            sum[i] = sum[i - (1 << j)] + nums[j];
        	break;
        }
    }
    
    // dp[i][j],考虑前i个桶,考虑取数情况为j,能获取到的每组最大值之间的最小值的结果为dp[i][j]
    int[][] dp = new int[k + 1][1 << n];
    // 初始化,一个桶取数为i,则结果就是其和,因为全部分到一个组
    for (int i = 0; i < (1 << n); i++)
        dp[1][i] = sum[i];
    
    // 从两个人开始递推
    for (int i = 2; i <= k; i++) {
        // 枚举每一种选择情况
        for (int j = 0; j < (1 << n); j++) {
			int minv = Integer.MAX_VALUE;
            // 枚举j的子集
            for (int s = j; s != 0; s = (s - 1) & j) {
                // 取最大值,从前i-1组选j-s和s的和中选择最大值
                // s被第i个人选择,j-s被前i-1选择
                int val = Math.max(dp[i - 1][j - s], sum[s]);
                // 转移方程
				minv = Math.min(minv, val);
            }
            dp[i][j] = minv;
        }
    }
    return dp[k][(1 << n) - 1];
}

0x04 字符串

1 KMP

static int[] next = new int[100005];
public static void main(String[] args) throws IOException {

    char[] s = "ABCDEFG".toCharArray();
    char[] p = "FG".toCharArray();

    getNext(p);
    System.out.println(kmp(0, s, p));
}

// 计算next数组
private static void getNext(char[] p) {
    int plen = p.length;
    int k = -1, j = 0;
    next[0] = -1;
    while (j < plen - 1) {
        if (k == - 1 || p[k] == p[j]) {
            ++k;
            ++j;
            next[j] = k;
        } else k = next[k];
    }
}

// 执行kmp算法
private static int kmp(int start, char[] s, char[] p) {
    int slen = s.length, plen = p.length;
    int i = start, j = 0;
    while (i < slen && j < plen) {
        if (j == -1 || s[i] == p[j]) {
            ++i;
            ++j;
        } else j = next[j];
    }
    return (j == plen ? i - j : -1);
}

2 字符串hash

static int maxn = (int) (1e6 + 5);
static BigInteger base = BigInteger.valueOf(131);
static BigInteger mod = BigInteger.valueOf(2).pow(64);
static BigInteger[] power = new BigInteger[maxn];
static BigInteger[] hash1 = new BigInteger[maxn];
static BigInteger hash2 = BigInteger.valueOf(0);

// 初始化 base^n
static void init() {
    power[0] = BigInteger.valueOf(1);
    for (int i = 1; i <= 10002; i++) {
        power[i] = power[i - 1].multiply(base);
    }
}

public static void main(String[] args) throws IOException {
    init();
    char[] s2 = next().toCharArray();
    char[] s1 = next().toCharArray();

    int len1 = s1.length;
    int len2 = s2.length;

    // 需要访问每一个子串的hash,用数组存
    hash1[0] = BigInteger.valueOf(0);
    for (int i = 1; i <= len1; i++)、
        // hash[i] = hash1[i - 1] * base + (s1[i - 1] - 'A' + 1))
        // hash[i] = hash[i] % mod;
        // base 和 mod 尽量互质,或者mod开的足够大也可以
        hash1[i] = hash1[i - 1].multiply(base)
        .add(BigInteger.valueOf(s1[i - 1] - 'A' + 1))
        .add(mod).mod(mod);

    // 只需要整个字符串的hash,直接存结果
    for (int i = 1; i <= len2; i++)
        hash2 = hash2.multiply(base)
        .add(BigInteger.valueOf(s2[i - 1] - 'A' + 1))
        .add(mod).mod(mod);

    int cnt = 0;
    for (int i = 0; i <= len1 - len2; i++) {
        BigInteger hash = hash1[i + len2]
            .subtract(hash1[i].multiply(power[len2]))
            .add(mod).mod(mod);
        if (hash.equals(hash2)) cnt++;
    }
    System.out.println(cnt);
}

3 最长回文长度 Manacher

private static int manacher(char[] cs) {
    StringBuilder sb = new StringBuilder();
    
    // 添加占位符去除长度奇偶性影响
    sb.append('$');
    for (char c : cs) sb.append('#').append(c);
    sb.append("#@");

    // 长度改变
    int len = cs.length * 2 + 1;
    cs = sb.toString().toCharArray();
    // maxv 最右回文串有边界, pos 最右回文串对称中心
    int maxv = 0, pos = 0, ans = 0;
    // length[i] 以i为对称中心,最长的回文半径,如abcba 回文半径3
    int[] length = new int[cs.length];
    // 从1开始,因为 `abc` 已经编变成 `$#a#b#c$@`
    for (int i = 1; i <= len; i++) {
        // i 位于maxv左侧,取 length[j] 和,maxv - i 最值
        // i 位于maxv右侧,保守估计,半径为1
        length[i] = (maxv > i ? Math.min(length[2 * pos - i], maxv - i) : 1);
        // 尝试中心扩展
        while (cs[i - length[i]] == cs[i + length[i]]) length[i]++;
        // 取最值
        ans = Math.max(ans, length[i]);
        // 更新最右侧回文串
        if (length[i] + i > maxv) {
            maxv = length[i] + i;
            pos = i;
        }
    }
	// 处理过后的回文串半径刚好为原串的半径 + 1
    return ans - 1;
}

4 单词查找 Trie树

// 假设字符串由小写字母构成
static int SIZE = 10000;
static int[][] trie = new int[SIZE][26];
static int tot = 1;
// 用于判断结尾
static boolean[] end = new boolean[SIZE];

static void insert(char[] str) {
    int len = str.length, p = 1;
    for (int i = 0; i < len; i++) {
        int ch = str[i] - 'a';
        if (trie[p][ch] == 0) trie[p][ch] = ++tot;
        p = trie[p][ch];
    }
    end[p] = true;
}

static boolean search(char[] str) {
    int len = str.length, p = 1;
    for (int i = 0; i < len; i++) {
        p = trie[p][str[i] - 'a'];
        if (p == 0) return false;
    }
    return end[p];
}

0x05 数学

1 GCD、LCM

//	最大公约数
int gcd(int a, int b) {
    int t;
    while (b) {
        t = a % b;
        a = b;
        b = t;
    }
    return a;
}
//	最小公倍数
int lcm(int a, int b) {	return a * b / gcd(a, b); }

2 素数

① 素数筛 O(N)

//	欧拉筛/线性筛
int 		size 	= 10000000;	//	经测试一般oj最大范围
int[] 		prime 	= new int[size];
boolean[] 	jd 		= new boolean[size];	// false 素数

int findPrime(int size) {
    jd[0] = jd[1] = true;

    int no = 0;
    for (int i = 2; i < size; i++) {
        //	符合条件,存储素数
        if (!jd[i])  prime[no++] = i;

        //	核心代码,注意边界
        for (int j = 0; j < no && prime[j] * i < size; j++) {
            jd[prime[j] * i] = true;
            //	如果可以整除退出
            if (i % prime[j] == 0) break;
        }
    }

    return no;
}
//	埃式筛(数据量大使用乘法容易爆内存,转用埃式)\
int 		size 	= 10000000;	//	经测试一般oj最大范围
int[] 		prime 	= new int[size];
boolean[] 	jd 		= new boolean[size];	// false 素数

int findPrime(int n) {
    int no = 0;
    int sqrtn = (int)Math.sqrt(n + 0.5);
    for (int i = 2; i <= sqrtn; i++) {
        if (jd[i]) continue;
        prime[no++] = i;
        for (int j = i * i; j <= n; j += i) 
            jd[j] = true;
    }
    for (int i = sqrtn + 1; i <= n; i++)
        if (!jd[i]) prime[++totPrimes] = i;
    return no;
}

② 判断单个素数

// 朴素
boolean isPrime(int n) {
    if (n <= 3) return n > 1;
    
    int sqrt = (int)Math.sqrt(n);
    for (int i = 2; i <= sqrt; i++) {
        if(n % i == 0) {
            return false;
        }
    }
    return true;
}
// 数论
boolean isPrime(int num) {
    if (num <= 3) return num > 1;
    // 不在6的倍数两侧的一定不是质数
    if (num % 6 != 1 && num % 6 != 5) return false;
    
    int sqrt = (int) Math.sqrt(num);
    for (int i = 5; i <= sqrt; i += 6) {
        if (num % i == 0 || num % (i + 2) == 0) {
            return false;
        }
    }
    return true;
}

3 快速幂

long Pow(long a, long n){
	long res = 1;
	while (n > 0) {
		if (n & 1 != 0) res *= a;
		a*=a;
		n>>=1;
    }
    return res;
}

long Mod_Pow(long a, long n, long mod){
    long res = 1;
    while (n > 0) {
        if (n & 1 != 0) res = (res * a) % mod;
        a = (a * a) % mod;
        n>>=1;
    }
    return res;
}

0x06 图论

1 最短路

① Floyd算法 (多源)

for (int k = 0; k < n; k++) 
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            map[i][j] = Math.min(map[i][j], map[i][k] + map[k][j]);

② Dijkstra算法 (无负边 单源)

public static void main(String[] args) throws IOException {
    int n = nextInt(), m = nextInt(), s = nextInt();
    int[][] map = new int[n + 1][n + 1];
    for (int i = 0; i <= n; i++)
        Arrays.fill(map[i], MAX);
    for (int i = 0; i < m; i++)
        map[nextInt()][nextInt()] = nextInt();

    int[] dis = new int[n + 1];
    Arrays.fill(dis, MAX);
    boolean[] used = new boolean[n + 1];
    dis[s] = 0;
    for (int k = 0; k < n; k++) {
        int x = -1;
        for (int t = 1; t <= n; t++)
            if (!used[t] && (x == -1 || dis[t] < dis[x]))
                x = t;

        used[x] = true;
        for (int y = 1; y <= n; y++)
            dis[y] = Math.min(dis[y], dis[x] + map[x][y]);
    }

    for (int i = 1; i <= n; i++) {
        System.out.print(dis[i] + " ");
    }
}

③ Dijkstra 优先队列(稠密图)

static final int MAX = Integer.MAX_VALUE / 2;

public static void main(String[] args) throws IOException {
    int n = nextInt(), m = nextInt(), s = nextInt();
    ArrayList<Edge>[] G = new ArrayList[n + 1];

    for (int i = 1; i <= n; i++)
        G[i] = new ArrayList<Edge>();

    for (int i = 0; i < m; i++)
        G[nextInt()].add(new Edge(nextInt(), nextInt()));

    int[] dis = new int[n + 1];
    Arrays.fill(dis, MAX);
    PriorityQueue<Edge> que = new PriorityQueue<>((a, b) -> a.len - b.len);
    que.add(new Edge(s, 0));
    dis[s] = 0;
    while (!que.isEmpty()) {
        Edge e = que.poll();
        int v = e.to;
        if (dis[v] < e.len) continue;
        for (int k = 0; k < G[v].size(); k++) {
            Edge edge = G[v].get(k);
            if (dis[edge.to] > dis[v] + edge.len) {
                dis[edge.to] = dis[v] + edge.len;
                que.add(new Edge(edge.to, dis[edge.to]));
            }
        }
    }

    for (int i = 1; i <= n; i++) {
        System.out.print(dis[i] + " ");
    }
}

static class Edge {
    int to, len;
    public Edge(int t, int l) {
        to = t;
        len = l;
    }
}

④ Bellman-Ford算法 (可负权)

int INF = Integer.MAX_VALUE / 2;
public int Bellman-Ford() {
    int[] dis = new int[n + 1];

    Arrays.fill(dis, INF);
    dis[k] = 0;

    // 核心代码
    int check = 1; // 优化
    for (int i = 1; i <= n; i++) {
        check = 1; // 优化
        for (int j = 0; j < times.length; j++) {
            if (dis[times[j][1]] > dis[times[j][0]] + times[j][2]) {
                dis[times[j][1]] = dis[times[j][0]] + times[j][2];
                check = 0;
            }
        }
        if (check == 1) break; // 优化
    }
}

⑤ SPFA算法 (Bellman-Ford 队列)

int INF = Integer.MAX_VALUE / 2;
public int Bellman-Ford() {
    List<Edge>[] map = new ArrayList[n + 1];

    int[] dis = new int[n + 1];
    Arrays.fill(dis, INF);
    dis[k] = 0;

    int[] que = new int[10000];
    int rear, front;
    front = rear = -1;

    que[++rear] = k;
    // 核心代码
    while (front != rear) {
        int t = que[++front];
        for (Edge e : map[t]) {
            if (dis[e.to] > dis[t] + e.len) {
                dis[e.to] = dis[t] + e.len;
                que[++rear] = e.to;
            }
        }
    }
}

class Edge {
    int len, to;
    public Edge(int t, int l) {
        len = l;
        to = t;
    }
}

2 最小生成树

① Prim算法 (点基准)

static int[][] map;
static int INF = Integer.MAX_VALUE;

public static void main(String[] args) throws IOException {
    int N = nextInt();
    map = new int[N + 1][N + 1];
    for (int i = 1; i <= N; i++) {
        Arrays.fill(map[i], INF);
    }

    // 记录地图
    for (int i = 0; i < N * (N - 1) / 2; i++) {
        int x = nextInt(), y = nextInt(), v = nextInt(), t = nextInt();
        map[x][y] = map[y][x] = (t == 1 ? 0 : v);
    }

    int[] lowcost = new int[N + 1];	// 生成树到该点最短距离
    int[] mst = new int[N + 1];		// 最短距离的起点


    // 核心代码
    // 每个点的前一位是起点、每个点的最短距离是起点到他的距离
    for (int i = 1; i <= N; i++) {
        lowcost[i] = map[1][i];
    }
    lowcost[1] = -1;

    for (int i = 1; i <= N; i++) {
        // 找到最近的点
        int min = INF;
        int to = 0;
        for (int j = 2; j <= N; j++) {
            if (lowcost[j] != -1 && lowcost[j] < min) {
                min = lowcost[j];
                to = j;
            }
        }
        // 将该点置于已经使用过的集合中
        lowcost[to] = -1;
        // 使用该点维护其他的点的最短距离
        for (int j = 1; j <= N; j++) {
            if (map[to][j] < lowcost[j]) {
                lowcost[j] = map[to][j];
                mst[j] = to;
            }
        }
    }
}

② Kruskal算法 (边基准)


public static void main(String[] args) throws IOException {
    int N = nextInt();
    init(N);

    // 优先队列
    PriorityQueue<Edge> que = new PriorityQueue<>((a, b) -> a.len - b.len);

    // 维护地图
    for (int i = 0; i < N * (N - 1) / 2; i++) {
        int x = nextInt(), y = nextInt(), v = nextInt(), t = nextInt();
        que.add(new Edge(x, y, (t == 1 ? 0 : v)));
    }

    // 核心代码
    int res = 0;
    while (!que.isEmpty()) {
        Edge e = que.poll();
        if (find(e.from) == find(e.to)) continue;

        res+=e.len;
        merge(e.from, e.to);
    }
    System.out.println(res);
}

private static class Edge {
    public int from, to, len;
    public Edge(int f, int t, int l) {
        from = f;
        to = t;
        len = l;
    }
}

// 并查集相关操作
static int[] fa;
static int find(int x) {
    while (fa[x] != x) x = fa[x] = fa[fa[x]];
    return x;
}
static void merge(int a, int b) { fa[find(b)] = find(a); }
static void init(int N) {
    fa = new int[N + 1];
    for (int i = 1; i <= N; i++) fa[i] = i;
}

0x07 数据结构

1 树

① 遍历

void preOrder(TreeNode root) {
    if (root == null) return;

    System.out.print(root.value + " ");
    preOrder(root.left);
    preOrder(root.right);
}

void inOrder(TreeNode root) {
    if (root == null) return;

    inOrder(root.left);
    System.out.print(root.value + " ");
    inOrder(root.right);
}

void postOrder(TreeNode root) {
    if (root == null) return;

    postOrder(root.left);
    postOrder(root.right);
    System.out.print(root.value + " ");
}

void layerOrder(TreeNode root) {
    Queue<TreeNode> q = new LinkedList<>();

    q.offer(root);
    root.layer = 0;

    while (!q.isEmpty()) {
        TreeNode temp = q.poll();
        System.out.print(temp.value + " ");

        if (temp.left != null) {
            q.offer(temp.left);
            temp.left.layer = temp.layer + 1;
        }

        if (temp.right != null) {
            q.offer(temp.right);
            temp.right.layer = temp.layer + 1;
        }
    }
}

② 重构

/* 前序和中序 */
TreeNode rebuildByPreAndIn(int preL, int preR, int inL, int inR) {
    if (preL > preR) return null;

    TreeNode root = new TreeNode(pre[preL]);

    int index = 0;
    for (int i = inL; i <= inR; i++) {
        if (in[i] == pre[preL]) {
            index = i;
            break;
        }
    }
    int numLen = index - inL;

    root.left = rebuildByPreAndIn(preL + 1, preL + numLen, inL, index - 1);
    root.right = rebuildByPreAndIn(preL + numLen + 1, preR, index + 1, inR);
    return root;
}

/* 后序和中序 */
TreeNode rebuildByPostAndIn(int postL, int postR, int inL, int inR) {
    if (postL > postR) return null;

    TreeNode root = new TreeNode(post[postR]);

    int index = 0;
    for (int i = inL; i <= inR; i++) {
        if (in[i] == post[postR]) {
            index = i;
            break;
        }
    }
    int numLen = index - inL;

    root.left = rebuildByPostAndIn(postL, postL + numLen - 1, inL, index - 1);
    root.right = rebuildByPostAndIn(postL + numLen, postR - 1, index + 1, inR);
    return root;
}

/* 层序和中序 */
TreeNode rebuildByLayerAndIn(Vector<Integer> layerT, int inL, int inR) {
    if (layerT.isEmpty() || inL > inR) return null;

    TreeNode root = new TreeNode(layerT.get(0));

    int index = 0;
    for (int i = inL; i <= inR; i++) {
        if (in[i] == layerT.get(0)) {
            index = i;
            break;
        }
    }

    Vector<Integer> layerL = new Vector<> ();
    Vector<Integer> layerR = new Vector<> ();

    for (int i = 1; i < layerT.size(); i++) {
        boolean flag = false;
        for (int j = inL; j < index; j++) {
            if (layerT.get(i) == in[j]) {
                flag = true;
                break;
            }
        }
        if (flag) layerL.add(layerT.get(i));
        else layerR.add(layerT.get(i));
    }

    root.left = rebuildByLayerAndIn(layerL, inL, index - 1);
    root.right = rebuildByLayerAndIn(layerR, index + 1, inR);
    return root;
}

2 树状数组

int bit[];
void BIT(vector<int> list) {
    bit = new int[nums.length + 1];
    for (int i = 1; i < bit.length; i++) bit[i] = nums[i - 1];
    for (int i = 1; i < bit.length; i++) {
        int j = i + (i & -i);
        if (j < bit.length) bit[j] += bit[i]; 
    }
}

void update(int pos, int x) {
	for (pos++; pos < bit.length; pos += (pos & -pos))
        bit[pos] += x; 
}

int getSum(int pos) {
	int sum = 0;
    for (pos++; pos > 0; pos -= (pos & -pos))
        sum += bit[pos];
    return sum;
}

3 堆(数组模拟)

int[] heap = new int[1025],  // 堆
	    hp = new int[1025],  // hp[i]=m; 堆中第i位,在原数组位于i
	    ph = new int[1025];  // ph[i]=m; 原数组第i位,在堆中位于m
int size;    // 堆的大小
void head_swap(int a, int b) {
    int tmp;
    tmp = ph[hp[a]]; ph[hp[a]] = ph[hp[b]]; ph[hp[b]] = tmp;
    tmp = hp[a]; hp[a] = hp[b]; hp[b] = tmp;
    tmp = h[a]; h[a] = h[b]; h[b] = tmp;
}

void down(int u) { // 与子节点比较
    int t = u;
    if (u * 2 <= cnt && h[2 * u] < h[t]) t = 2 * u;
    if (u * 2 + 1 <= cnt && h[2 * u + 1] < h[t]) t = 2 * u + 1;
    if (t != u) {
        head_swap(u, t);
        down(t);
    }
}

void up(int u) { // 与父节点比较
    while (u >> 1 > 0 && h[u >> 1] > h[u]) {
        head_swap(u >> 1, u);
        u >>= 1;
    }
}

public static void main(String[] args) {
    // 小根堆
	// 插入
    heap[++size] = key;
    ph[i] = size;
    hp[size] = i;
    up(size);
    
    // 最小值
    heap[1];
    
    // 删除最小值
    heap[1] = heap[size--]; down(1);
    
    // 删除任意一个元素
    heap[k] = heap[size--]; 
    down(k); 
    up(k); 
    
    // 修改任意一个元素
    heap[k] = x; 
    down(k); 
    up(k);	
}

4 线段树

int[] node = new int[size];
int[] nums = new int[length];
int[] token = new int[size];
void build(int start, int end, int pos) {
	if (start == end) {
        node[pos] = nums[start];
        return;
    }
    int mid = ((end = start) >> 1) + start;
    build(start, mid, pos << 1);
    build(mid + 1, end, (pos << 1) | 1);
    node[pos] = node[pos << 1] + node[(pos << 1) | 1];
    return;
}

int getsum(int l, int r, int s, int e, int p) {
	if (l <= s && r >= e) return node[pos];
	int m = ((e - s) >> 1) + s;
	if (token[pos] != 0) {
		node[p << 1] += token[p] * (m - s + 1);
		node[(p << 1) | 1] += token[p] * (e - m);
		token[p << 1] += token[p];
		token[(p << 1) | 1] += token[p];
		token[p] = 0;
    }
	int sum = 0;
	if (l <= m) sum += getsum(l, r, s, m, p << 1);
	if (r > m) sum += getsum(l, r, m + 1, e, (p << 1) | 1);
	return sum;
}

void update(int l, int r, int c, int s, int e, int p) {
	if (l <= s && e <= r) {
		node[p] += (t - s + 1) * c;
		token[p] += c;
		return;
    }
    int m = ((e - s) >> 1) + s;
    if (token[p] != 0 && s != e) {
		node[p << 1] += token[p] * (m - s + 1);
		node[(p << 1) | 1] += token[p] * (e - m);
		token[p << 1] += token[p];
		token[(p << 1) | 1] += token[p];
		token[p] = 0;
    }
    if (l <= m) update(l, r, c, s, m, p << 1);
    if (r > m) update(l, r, c, m + 1, e, (p << 1) | 1);
    node[p] = node[p << 1] + node[(p << 1) | 1];
    return;
}
// 基于求「区间和」以及对区间进行「加减」的更新操作
// 惰性标记  + 动态开点
public class SegmentTreeDynamic {
    
    class Node {
        // 左右孩子节点
        Node left, right;
        // 当前节点值
        int val;
        // 懒惰标记
        int add;
    }
    
    private int N = (int) 1e9;
    private Node root = new Node();
    
    public void update(Node node, int start, int end, int l, int r, int val) {
        // 找到满足要求的区间
        if (l <= start && end <= r) {
            // 区间节点加上更新值
            // 注意:需要✖️该子树所有叶子节点
            node.val += (end - start + 1) * val;
            // 添加懒惰标记
            node.add += val;
            return;
        }
        int mid = (start + end) >> 1;
        // 下推标记
        pushDown(node, mid - start + 1, end - mid);
        // [start, mid][l, r] 可能有交集,遍历左孩子区间
        if (l <= mid) update(node.left, start, mid, l, r, val);
        // [mid + 1, end][l, r] 可能有交集,遍历右孩子区间
        if (r > mid) update(node.right, mid + 1, end, l, r, val);
        // 向上更新
        pushUp(node);
    }
    
    // 在区间 [start, end] 中查询区间 [l, r] 的结果,即 [l ,r] 保持不变
    // 对于上面的例子,应该这样调用该函数:query(root, 0, 4, 2, 4)
    public int query(Node node, int start, int end, int l, int r) {
        // 区间 [l ,r] 完全包含区间 [start, end]
        // 例如:[2, 4] = [2, 2] + [3, 4],当 [start, end] = [2, 2] 或者 [start, end] = [3, 4],直接返回
        if (l <= start && end <= r) return node.val;
        // 把当前区间 [start, end] 均分得到左右孩子的区间范围
        // node 左孩子区间 [start, mid]
        // node 左孩子区间 [mid + 1, end]
        int mid = (start + end) >> 1, ans = 0;
        // 下推标记
        pushDown(node, mid - start + 1, end - mid);
        // [start, mid][l, r] 可能有交集,遍历左孩子区间
        if (l <= mid) ans += query(node.left, start, mid, l, r);
        // [mid + 1, end][l, r] 可能有交集,遍历右孩子区间
        if (r > mid) ans += query(node.right, mid + 1, end, l, r);
        // ans 把左右子树的结果都累加起来了,与树的后续遍历同理
        return ans;
    }
    
    private void pushUp(Node node) {
        node.val = node.left.val + node.right.val;
    }
    
	private void pushDown(Node node, int leftNum, int rightNum) {
        // 动态开点
        if (node.left == null) node.left = new Node();
        if (node.right == null) node.right = new Node();
        // 如果 add 为 0,表示没有标记
        if (node.add == 0) return ;
        // 当前节点加上标记值
        // 注意:需要✖️该子树所有叶子节点
        node.left.val += node.add * leftNum;
        node.right.val += node.add * rightNum;
        // 把标记下推给孩子节点
        node.left.add = node.add;
        node.right.add = node.add;
        // 取消当前节点标记
        node.add = 0;
    }
}

\