第2章 基础算法

2.1 初级

(1)尺取法

⭐ 反向扫描(左右指针)

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		for( ; n > 0; n--) {
			char[] s = sc.next().toCharArray();
			boolean ans = true;
			int i = 0, j = s.length - 1;
			while(i < j) {
				if (s[i] != s[j]) {
					ans = false; break;
				}
				i++; j--;
			}
			System.out.println(ans ? "yes" : "no");
		}
	}
}

⭐ 同向扫描(快慢指针)

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int S = sc.nextInt();
		int[] a = new int[n];
		for(int i = 0; i < n; i++)
			a[i] = sc.nextInt();
		int left = 0, right = 0, val = 0, ans = n + 1;
		while (right < n) {
			while (val >= S) {
				ans = Math.min(ans, right - left);
				val -= a[left++];
			}
			val += a[right++];
		}
		System.out.println(ans == n + 1 ? 0 : ans);
		sc.close();
	}
}

(2)二分法

⭐ 整数二分
1 在单调递增序列中找左边 xx 的后继(第一个大于 x 的数)

def binSearchBack(arr: List, x: int) -> int:
    # 当 x 大于所有值返回 N 故 right 赋值为 N
    left, right = 0, len(arr)
    while left < right:
        mid = (left + right) >> 1  # 左中位数
        if arr[mid] >= x:
            right = mid
        else:
            left = mid + 1
    return left

2 在单调递增序列中找右边 xx 的前驱(最后一个小于 x 的数)

def binSearchFront(arr: List, x: int) -> int:
    # 当 x 小于所有值返回 -1 故 left 赋值为 -1
    left, right = -1, len(arr) - 1
    while left < right:
        mid = (left + right + 1) >> 1  # 右中位数
        if arr[mid] <= x:
            left = mid
        else:
            right = mid - 1
    return left
import java.util.Scanner;
import java.util.Arrays;

public class Main {
	static int N, C;
	static int[] x;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		N = sc.nextInt();
		C = sc.nextInt();
		x = new int[N];
		for(int i = 0; i < N; i++)
			x[i] = sc.nextInt();
		Arrays.sort(x);
		int left = 1, right = x[N - 1] - x[0];
		// 单调递减找左边第一个满足的值
		while(left < right) {
			// (3)由于左边不多移动 所以使用右中位数
			int mid = (left + right + 1) >> 1;
			if(check(mid))
				left = mid; // (1)满足条件 左边不多移动一格
			else
				right = mid - 1; // (2)不满足条件 右边需要多移动一格
		}
		System.out.println(left);
		sc.close();
	}

	static boolean check(int dis) {
		int cnt = 1, place = 0; // 第一个牛放第一个
		for(int i = 1; i < N; i++) {
			if(x[i] - x[place] >= dis) {
				cnt += 1; place = i;
			}
			if(cnt >= C) return true;
		}
		return false;
	}
}

⭐ 实数二分

import java.util.Scanner;

public class Main {
	static int T, N, F;
	static double eps = 1e-5;
	static double[] areas;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		T = sc.nextInt();
		for( ; T > 0; T--) {
			N = sc.nextInt(); F = sc.nextInt();
			areas = new double[N];
			double left = 0, right = 0;
			for(int i = 0; i < N; i++) {
				double r = sc.nextDouble();
				areas[i] = Math.PI * r * r;
				right = Math.max(right, areas[i]);
			}

			while(right - left > eps) {
				double mid = left + (right - left) / 2;
				if(check(mid))
					left = mid;
				else
					right = mid;
			}
			System.out.println(String.format("%.4f", left));
		}
	}

	static boolean check(double area) {
		int cnt = 0;
		for(int i = 0; i < N; i++) {
			cnt += (int)(areas[i] / area);
			if(cnt >= F + 1) return true;
		}
		return false;
	}
}

(3)三分法

⭐ 实数三分

import java.util.Scanner;

public class Main {
	static int N;
	static double eps = 1e-6;
	static double[] a;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		N = sc.nextInt(); a = new double[N + 1];
		double l = sc.nextDouble();
		double r = sc.nextDouble();
		for(int i = N; i >= 0; i--)
			a[i] = sc.nextDouble();
		while(r - l > eps) {
			double k = (r - l) / 3.0;
			double mid1 = l + k, mid2 = r - k;
			if(f(mid1) > f(mid2))
				r = mid2;
			else
				l = mid1;
		}
		System.out.println(String.format("%.5f", l));
	}

	static double f(double x) {
		double ans = 0;  // 多项式乘法
		for(int i = N; i >= 0; i--) 
			ans = ans * x + a[i];
		return ans;
	}
}

⭐ 整数三分

import java.util.Scanner;

public class Main {
	static long A, B, C, ans = Long.MAX_VALUE;
	static int n, m, tMin = Integer.MAX_VALUE;
	static int[] t, b;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		A = sc.nextLong(); B = sc.nextLong(); C = sc.nextLong();
		n = sc.nextInt(); m = sc.nextInt();
		t = new int[n]; b = new int[m];
		for(int i = 0; i < n; i++) {
			t[i] = sc.nextInt(); tMin = Math.min(tMin, t[i]);
		}
		for(int i = 0; i < m; i++) b[i] = sc.nextInt();

		if(C >= 1e16) {  // 有特例需要特判 否则导致数字过大
			// 由于 C 过大 故所有科目均须在最小期望天数之前公布
			ans = calc1(tMin);
		} else {
			// 不愉快度是时间的下凹函数 采取整数三分策略
			int L = 1, R = 100000;
			while(R - L > 2) {
				int mid1 = L + (R - L) / 3, mid2 = R - (R - L) / 3;
				long c1 = calc1(mid1) + calc2(mid1);  // c1 总不满意度
				long c2 = calc1(mid2) + calc2(mid2);  // c2 总不满意度
				if(c1 > c2) L = mid1;
				else R = mid2;
			}
			for(int i = L; i <= R; i++)  // 寻找此区间的最小值
				ans = Math.min(ans, calc1(i) + calc2(i));
		}
		System.out.println(ans);
	}

	static long calc1(int time) { // 所有科目在 time 之前公布的不满意度
		// 不满意度来自操作1 和 操作2
		long x = 0, y = 0;  // x 记录需增加天数 y 记录需减少天数
		for(int i = 0; i < m; i++) {
			if(b[i] > time) y += b[i] - time;
			else x += time - b[i];
		}
		if(A < B) { // 由于操作1 会导致 +1 和 -1 故最大操作次数为 min(x, y)
			// 增加的天数可以不增加 减少的天数必须减少 故剩余天数使用操作二执行
			return Math.min(x, y) * A + (y - Math.min(x, y)) * B;
		} else { // 如果操作2 的花费小于操作1 的花费 只需使用操作2
			return y * B;  // 只需将超过 time 的科目变为 time 即可
		}
	}

	static long calc2(int time) { // 所有科目在 time 之前公布学生的不满意度
		long sum = 0;
		for(int i = 0; i < n; i++)
			sum += time > t[i] ? (time - t[i]) * C : 0;
		return sum;
	}
}

(4)排序与排列

⭐ 排序

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int N = sc.nextInt();
		int[][] stus = new int[N][5];
		for(int i = 0; i < N; i++) {
			stus[i][0] = i + 1; stus[i][1] = sc.nextInt();
			stus[i][2] = sc.nextInt(); stus[i][3] = sc.nextInt();
			stus[i][4] = stus[i][1] + stus[i][2] + stus[i][3];
		}
		Arrays.sort(stus, Comparator.comparingInt(
				(int[] stu) -> -stu[4]).thenComparingInt(
						stu -> -stu[1]).thenComparingInt(
						stu -> stu[0]));
		for(int i = 0; i < 5; i++) {
			System.out.println(stus[i][0] + " " + stus[i][4]);
		}
	}
}

⭐ 排列

public class Main {
	static boolean[] vis = new boolean[14];
	static int[] b = new int[14];
	static int ans;
	public static void main(String[] args) {
		dfs(1); // 从第一个空开始填写数字
		System.out.println(ans);
	}

	static void dfs(int cur) {
		if(cur == 13) {
			if(b[10] * b[11] == b[12])
				ans++;
			return;
		}
		if(cur == 4 && b[1] + b[2] != b[3]) return;
		if(cur == 7 && b[4] - b[5] != b[6]) return;
		if(cur == 10 && b[7] * b[8] != b[9]) return;
		for(int i = 1; i <= 13; i++) {
			if(!vis[i]) {
				vis[i] = true;
				b[cur] = i; dfs(cur + 1);
				vis[i] = false;
			}
		}
	}
}

2.2 中级

(1)倍增法与ST算法

(2)前缀和与差分

(3)离散化

(4)分治法

(5)贪心法与拟阵

posted @   Koonan-Edogawa  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示