【算法练习】蓝桥杯日常练习系统(礼物、数的潜能、娜神平衡、粘木棍、车的放置、最大分解、Substrings、线性筛、乘积最大、递增三元组、倍数问题、结账问题、无聊的逗)

一、礼物(前缀和 + 二分答案)

在这里插入图片描述
对石子个数进行二分,然后去check当前石子数是否满足要求,一直逼近石子的最大值即可,关键在于要处理好前缀和的下标关系。例如,现在从 i 的位置开始划分,取k个石子,前面k个石子的重量 = preSum[i] - preSum[i - k],后面k个石子的重量 = preSum[i + k] - preSum[i]。

另外就是用BufferReader加快读取速度。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class Main {
	static long[] preSum;
	static int n;
	static long s;
	static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
	public static void main(String[] args) throws IOException {
		String[] tmp = reader.readLine().split(" ");
		n = Integer.parseInt(tmp[0]);
		s = Long.parseLong(tmp[1]);
		long[] stones = new long[n + 1];
		preSum = new long[n + 1];
		tmp = reader.readLine().trim().split(" ");
		for (int i = 1; i <= n; i++) {
			stones[i] = Long.parseLong(tmp[i - 1]);
			// 求前缀和
			preSum[i] = preSum[i - 1] + stones[i];
		}
		// 找最大的 k 使得题意满足
		int left = 1;
		int right = n;
		int mid = 0;
		int k = 0;
		while (left <= right) {
			mid = left + (right - left) / 2;
			if (check(mid)) {
				// 满足条件就继续往大的找
				k = mid;
				left = mid + 1;
			} else {
				right = mid - 1;
			}
		}
		System.out.println(k * 2);
	}
	static boolean check(int k) {
		// 遍历所有可能的拆分点
		for (int i = k; i <= n - k; i++) {
			if (preSum[i] - preSum[i - k] <= s && preSum[i + k] - preSum[i] <= s) {
				return true;
			}
		}
		return false;
	}
}

二、数的潜能(数学推导)

在这里插入图片描述
看了其他人解法才知道,要把给定的数尽可能多的分出3,其次再分出2,如果分出1,那就加在之前分出3的结果后面,需要用快速幂加快运算,注意在快速幂的过程中进行取模运算,同时注意,快速幂的写法。

import java.io.*;
import java.util.*;

public class Main {
	static Long mod = 5218L;
	static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		long n = scan.nextLong();
		if (n == 1) System.out.println(1);
		else if (n == 2) System.out.println(2);
		else if (n == 3) System.out.println(3);
		else if (n == 4) System.out.println(4);
		else if (n == 5) System.out.println(6);
		else {
			long resule = 0L;
			// 记录3的个数
			long num = 0L;
			if (n % 3 == 0) {
				// 能够被三整除,那就直接是3的幂
				num = n / 3;
				resule = quickPow(3, num);
			} else if (n % 3 == 1) {
				// 余数 = 1,把剩余的1加到一个3上,成为4
				num = n / 3 - 1;  // 拿一个3去凑成4
				resule = quickPow(3, num) * 4 % mod;
			} else {
				// 余数 = 2,直接把2乘进去
				num = n / 3;
				resule = quickPow(3, num);
				resule = (2 * resule) % mod;
			}
			System.out.println(resule);
		}
	}
	static long quickPow(long m, long n) {
		if (n == 0) return 1;
		else if (n % 2 == 1) {
			// 奇数次方
			return quickPow(m, n - 1) * m % mod;
		} else {
			// 先把tmp算出来加快运算速度
			long tmp = quickPow(m, n / 2) % mod;
			return tmp * tmp % mod;
			// 结尾还要 % mod,因为(a * b) % mod = (a % mod * b % mod) % mod
		}
	}
}

三、娜神平衡(状态搜索)

在这里插入图片描述
题目很简单,模拟这样一个把一个数组元素分成两个数组的过程,求出满足题意的方案,由于需要分为两个数组,并且每次都是遍历当前情况是否满足条件,很容易想到状态压缩,用二进制串表示两个数组AB的选择,0进B数组,1进A数组,当然要避免全0、全1的情况。

如何看当前A、B分组情况是否满足题意呢?很简单,我们把两个数组拿出来模拟一遍即可!同样先拿A数组中的元素(符合题意),然后看这个过程中是否能够让A、B数组的和满足题意,最后看两个数组的元素是否都遍历完了,没有遍历完说明是不满足题意的。

import java.io.*;
import java.util.*;

public class Main {
	static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));
	// A B数组的下标
	static int idxa = 0, idxb = 0;
	static int[] A, B;
	static int r = 0;
	public static void main(String[] args) throws IOException {
		String[] in = reader.readLine().trim().split(" ");
		int n = Integer.parseInt(in[0]);
		r = Integer.parseInt(in[1]);
		int[] num = new int[n];
		in = reader.readLine().trim().split(" ");
		for (int i = 0; i < n; i++) {
			num[i] = Integer.parseInt(in[i]);
		}
		// 先记录下第一个数
		int first = num[0];
		Arrays.sort(num);
		// 状态搜索,n个数,用1表示在A数组中,用0表示在B数组中
		for (int i = 1; i < (1 << n) - 1; i++) {
			// 要排除全为0、全为1的情况
			idxa = 0;
			idxb = 0;
			A = new int[n];
			B = new int[n];
			for (int j = 0; j < n; j++) {
				// 当前位为1就存A
				if (((1 << j) & i) != 0) {  // 注意一定是不等于0,而不是一定等于1
					A[idxa++] = num[j];
				} else {
					B[idxb++] = num[j];
				}
			}
			// 每个数都放好位置了
			if (check()) {
				// 找存第一个数字的数组
				boolean is = false;
				for (int j = 0; j < idxa; j++) {
					if (A[j] == first) {
						is = true;
						break;
					}
				}
				if (is) {
					for (int j = 0; j < idxa; j++) {
						log.write(A[j] + " ");
					}
					log.write("\n");
					for (int j = 0; j < idxb; j++) {
						log.write(B[j] + " ");
					}
				} else {
					for (int j = 0; j < idxb; j++) {
						log.write(B[j] + " ");
					}
					log.write("\n");
					for (int j = 0; j < idxa; j++) {
						log.write(A[j] + " ");
					}
				}
				break;
			}
		}
		log.flush();
	}
	static boolean check() {
		int i = 0, j = 0, suma = 0, sumb = 0;
		// 按照题意,第一个数先放A数组
		suma += A[i++];
		boolean flag = true;
		while (Math.abs(suma - sumb) <= r) {
			flag = false;
			// 如果A中当前数可以放A,那就放A
			if (i < idxa && Math.abs(suma + A[i] - sumb) <= r) {
				suma += A[i++];
				flag = true;
			}
			// 如果B中当前数可以放B,那就放B
			if (j < idxb && Math.abs(suma - B[j] - sumb) <= r) {
				sumb += B[j++];
				flag = true;
			}
			if (flag == false) {
				break;
			}
		}
		if (i == idxa && j == idxb) return true;
		return false;
	}
}

BTW,蓝桥杯的这些题总是喜欢给一个故事场景,然后再引出问题,需要对问题有很好的抽象能力,而LeetCode则是喜欢直接了当的给出题目信息,面对需要抽象出算法模型的题目,一定要仔细阅读题意,抓住每个点,再根据数据范围来选择不同算法。例如:小范围:搜索、状态搜索,范围过大:DP、快速幂、二分等。

※四、粘木棍(DFS)

在这里插入图片描述
唯一需要搜索的是每一根木棍的分组,所以我们可以在dfs中写一个分组搜索,每个木棍都可以放到0 - m-1号的分组中,遍历每根木棍即可,木棍放进去后,当前分组的长度 + 该木棍长度,回溯时,要减回去!

import java.io.*;
import java.util.*;

public class Main {
	static int[] sticks;
	static int[] groups;
	static boolean[] vis;
	static int n, m;
	static int ans = Integer.MAX_VALUE;
	static LinkedList<Integer> tmp = new LinkedList<>();
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		n = scan.nextInt();
		m = scan.nextInt();
		// m个组
		groups = new int[m];
		Arrays.fill(groups, -1);
		// n个木棍
		sticks = new int[n];
		vis = new boolean[n];
		for (int i = 0; i < n; i++) {
			sticks[i] = scan.nextInt();
		}
		// 需要知道每根木棍放到了哪个组里
		dfs(0);
		System.out.println(ans);
	}
	// 考虑第 k 根木棍
	static void dfs(int k) {
		// n根木棍考虑完
		if (k == n) {
			System.out.println(tmp);
			int max = groups[0], min = groups[0];
			for (int i = 1; i < m; i++) {
				max = Math.max(max, groups[i]);
				min = Math.min(min, groups[i]);
			}
			ans = Math.min(ans, max - min);
			return;
		}
		for (int i = 0; i < m; i++) {
			// 遍历m个分组
			groups[i] += sticks[k];
			tmp.add(i);
			dfs(k + 1);
			groups[i] -= sticks[k];
			tmp.removeLast();
		}
	}
}

在这里插入图片描述
可以很清楚的看到3根木棍的各自组号。

五、车的放置(DFS)

与8皇后不同的是,车的放置不需要将棋盘都摆满,当有某行或某列空出来也可以。最后用深度搜索加回溯解决。

还有一个需要重点处理的问题,我们如何统计ans,需要遍历马的个数吗?答案是否定的,可以手画一下这道题的搜索过程,就会发现,这个过程其实已经遍历了马的个数,并且也考虑了马放在不同位置的情况(也即:马个数小于棋盘n)
在这里插入图片描述

import java.io.*;
import java.util.*;

public class Main {
	static boolean[] vis;
	static int n;
	static int ans = 1;
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		n = scan.nextInt();
		// 从第0行开始遍历
		vis = new boolean[n];
		dfs(0);
		System.out.println(ans);
	}
	static void dfs(int row) {
		if (row == n) return;
		for (int i = 0; i < n; i++) {
			// 当前行有n列
			if (vis[i]) continue;
			// 选中当前列
			vis[i] = true;
			// 在遍历每个行的每个列时,就把答案进行记录,因为无论如何总是有放1个马,2个马的情况
			// 在这个过程中已经对马的个数进行了遍历
			ans++;
			// 遍历下一行
			dfs(row + 1);
			// 回溯
			vis[i] = false;
		}
		// 也可以当前行不放(这句代码很关键,不然是没法实现全部马的个数的方案数的统计)
		dfs(row + 1);
	}
}

六、最大分解(模拟)

在这里插入图片描述
先找到n的所有因子(可以先把n加进去,然后只用遍历1 - n/2,减少遍历时间(避免质数)),然后降序排列,从大到小遍历因子,如果找到了当前因子的因子,那就可以加上,然后再继续往下遍历,注意不要重复遍历因子(下一次遍历的因子应该在加的因子之后),因为降序排列,所以保证了累加和最大。

import java.io.*;
import java.util.*;

public class Main {
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int[] nums = new int[n];
		int idx = 0;
		// 找n的因子(先把n自己放进去,避免质数)
		nums[idx++] = n;
		for (int i = n / 2; i >= 1; i--) {
			// 不用管质数的情况,后面会说明
			if (n % i == 0) {
				nums[idx++] = i;
			}
		}
		// 注意因子一定要降序排列
		int sum = 0;
		int i = 0, j = 0;
		// 考虑质数17,只能写成 17 > 1,结果=1,idx一定是=2,也满足题意
		for (i = 0; i < idx; i++) {
			for (j = i + 1; j < idx; j++) {
				// 因子 i 一定大于因子 j
				if (nums[i] % nums[j] == 0) {
					// 互为因子的因子
					sum += nums[j];
					break;
				}
			}
			// 更新下一次因子遍历的开始下标
			i = j - 1;  // -1是因为 for 循环 i++,又变成i = j
		}
		System.out.println(sum);
	}
}

七、Substrings(模拟)

在这里插入图片描述
题目意思很简单,让你找给定几个串的最大子串长度,这个子串可以是其它串的逆序串、正串都可以。

以长度最小的字符串为依据,构建多个子串和其反串,用这个子串和反串去遍历剩余的其它串,看是否满足题目需求,满足了,就来更新最大字串长度。

import java.io.*;
import java.util.*;

public class Main {
	static String[] in;
	static int m;
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		int n = Integer.parseInt(scan.nextLine());
		while (n-- > 0) {
			m = Integer.parseInt(scan.nextLine());
			in = new String[m];
			int idx = 0;
			for (int i = 0; i < m; i++) {
				in[i] = scan.nextLine();
				// 找最小字符串
				if (in[i].length() < in[idx].length()) {
					idx = i;
				}
			}
			int ans = 0;
			// 用最小的字符串去生成子串
			for (int i = 0; i < in[idx].length(); i++) {
				for (int j = i; j < in[idx].length(); j++) {
					String sub = in[idx].substring(i, j + 1);
					if (check(sub, idx)) {
						// 如果当前子串满足要求,就更新答案
						ans = Math.max(ans, j - i + 1);
					}
				}
			}
			System.out.println(ans);
		}
	}
	static boolean check(String sub, int idx) {
		// 求反转字符串,因为题目要求:不论反转、正常都可以
		String rever = new StringBuilder(sub).reverse().toString();
		for (int i = 0; i < m; i++) {
			// 自己就不判断了
			if (idx == i) continue;
			if (in[i].indexOf(sub) == -1 && in[i].indexOf(rever) == -1) return false;
		}
		return true;
	}
}

需要注意的是,如果scaner用了nextLine(),就不要出现nextInt(),会读取出错。

八、线性筛(纯纯模板)

	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int[] prime = new int[n + 1];
		int idx = 0;
		long sum = 0L;
		boolean[] isPrime = new boolean[n + 1];
		for (int i = 2; i <= n; i++) {
			if (isPrime[i] == false) {
				prime[idx++] = i;
			}
			for (int j = 0; j < idx; j++) {
				if (i * prime[j] > n) break;
				isPrime[i * prime[j]] = true;
				// 保证每个数只被最小质因数分解,加快时间
				if (i % prime[j] == 0) break;
			}
		}
	}

九、乘积最大(数学、贪心)

在这里插入图片描述
注意题目中说的,对负数取余是负余数,而不用变成正的,意思就是直接取余就行,不用:(a % mod + mod) % mod

回到本题思路,考虑k == n,肯定全拿,把数组元素升序排序,考虑k为偶数,结果一定为正数,考虑k为奇数,我们要先把最大值拿下来,k–,转换为偶数,这里可能有同学会问,为什么要把最大值拿下来,这里可以分情况讨论:全为负数、全为正数、一半正一半负,三种情况,都可以发现,把最大值拿下来都是必须的。拿下来之后,就可以按照k为偶数的情况进行处理。

那么,k为偶数的情况该如何处理?为偶数时,一定要两两取数,这样才能保证负负得正,不会漏掉负负相乘的情况,所以很简单,每次从头部、尾部分别拿两个元素,比较乘积大小,选择乘积更大的与ans相乘即可。

import java.io.*;
import java.util.*;

public class Main {
	static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int k = scan.nextInt();
		long[] nums = new long[n];
		for (int i = 0; i < n; i++) {
			nums[i] = scan.nextLong();
		}
		int mod = 1000000009;
		// 从小到大排序
		Arrays.sort(nums);
		long ans = 1;
		// 考虑 n == k 全部拿
		// k 为偶数,结果肯定>0,不论是否有负数,或者全为负数
		// 因为要避免既有负数又有偶数的情况,所以每次取头两个,和尾两个进行乘积比较
		// k 为奇数,如果最大值已经为负数了,那只能让负数尽可能大一点,拿掉最大值后,就可以转换为k为偶数的情况
		// 当然如果最大值为负数,k为偶数后要尽可能去找两两乘积的最小值(这样乘上最大值负数后才能使得乘积最大)
		int flag = 1;
		int i = 0;
		int j = n - 1;
		if (k % 2 == 1) {
			// k是奇数,先转换为偶数情况
			ans = nums[j--];
			if (ans < 0) {
				flag = -1;
			}
			// 需要的个数-1
			k--;
		}
		// 下面直接讨论k为偶数的情况
		while (k > 0) {
			long a = (long)(nums[i] * nums[i + 1]);
			long b = (long)(nums[j] * nums[j - 1]);
			// 选取前后两坨更大的,当然要考虑如果最大数为负数的情况,flag = -1
			if (a * flag > b * flag) {
				ans = (a % mod * ans % mod) % mod;
				i += 2;
			} else {
				ans = (b % mod * ans % mod) % mod;
				j -= 2;
			}
			// 拿了两个数
			k -= 2;
		}
		System.out.println(ans);
	}
}

十、递增三元组(枚举、双指针)

在这里插入图片描述
样例输出:27

我们去枚举中间数,找A数组中有多少数小于当前数,找B数组中有多少数大于当前数即可。

import java.io.*;
import java.util.*;

public class Main {
	static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter log = new BufferedWriter(new OutputStreamWriter(System.out));
	static HashMap<String, Integer> zodiac = new HashMap<>();
	public static void main(String[] args) throws IOException {
		Scanner scan = new Scanner(System.in);
		int n = Integer.parseInt(scan.nextLine());
		int[] A = new int[n];
		int[] B = new int[n];
		int[] C = new int[n];
		for (int i = 0; i < n; i++) {
			A[i] = scan.nextInt();
		}
		for (int i = 0; i < n; i++) {
			B[i] = scan.nextInt();
		}
		for (int i = 0; i < n; i++) {
			C[i] = scan.nextInt();
		}
		Arrays.sort(A);
		Arrays.sort(B);
		Arrays.sort(C);
		long cnt = 0;
		int p = 0;
		int q = 0;
		for (int i = 0; i < n; i++) {
			// 以中间的数为依据,去找A数组 >= 中间数的下标p,那么前面就有p个数小于当前中间数
			// 去找B数组中 > 中间数的开始位置q,那么后面就有n - q个数大于当前中间数
			while (p < n && A[p] < B[i]) p++;
			while (q < n && C[q] <= B[i]) q++;
			cnt += (long)(p * (n - q));
		}
		System.out.println(cnt);
	}
}

※十一、倍数问题(取模运算问题)

在这里插入图片描述
取模运算问题,一直以来都是一个十分头痛的问题,不知道什么时候 +,什么时候-,或者又什么时候%了,借助本题,把取模运算的问题,全都给解决了,分析透彻,因为蓝桥杯很喜欢考取模,各种问题要么就是直接取模,要么就是转换为取模问题。

首先是括号展开的问题:
首 先 , ( a + b ) % k = ( a % k + b % k ) % k , 减 法 同 理 , a ∗ b % k = ( a % k ∗ b % k ) % k 首先,(a + b)\%k = (a\%k + b\%k) \%k,减法同理,a * b \%k = (a\%k * b\%k)\%k (a+b)%k=(a%k+b%k)%kab%k=(a%kb%k)%k
一定要注意展开后,里面每个数都要分配一个k,最后合起来还要再对k取模。

下面再看看用取模方程求未知数的问题:
现 在 知 道 ( a + b ) % k = 0 , 也 就 是 ( a % k + b % k ) % k = 0 , 现 在 已 知 a % k , 问 b % k 为 多 少 ? 不 妨 假 设 a % k + b % k = k ( 不 能 假 设 为 0 ) , 那 么 b % k = k − a % k , 对 吗 ? 当 a % k = 0 时 , 显 然 是 错 误 的 所 以 , 还 需 要 对 k 取 余 , 避 免 上 述 情 况 , 最 终 结 果 : b % k = ( k − a % k ) % k 现在知道(a+b)\%k =0,也就是(a\%k + b\%k) \%k = 0,现在已知a\%k,问b\%k为多少?\\ 不妨假设 a\%k + b\%k = k(不能假设为0),那么b\%k = k - a\%k,对吗?当a\%k = 0时,显然是错误的\\ 所以,还需要对k取余,避免上述情况,最终结果:b\%k = (k - a\%k) \%k (a+b)%k=0(a%k+b%k)%k=0a%kb%ka%k+b%k=k(0)b%k=ka%ka%k=0kb%k=(ka%k)%k

上面介绍了两个数的情况,下面来看看三个数的情况:
同 样 的 道 理 , c % k = ( k − a % k − b % k ) % k , 对 吗 ? a % k + b % k 肯 定 会 超 过 k 的 范 围 所 以 应 该 处 理 为 : ( k − ( a % k + b % k ) % k ) % k , 这 样 就 可 以 避 免 超 过 k 范 围 的 情 况 同样的道理,c\%k = (k - a \%k - b\%k)\%k,对吗?a\%k + b\%k肯定会超过k的范围\\ 所以应该处理为:(k - (a\%k+b\%k)\%k)\%k,这样就可以避免超过k范围的情况 c%k=(ka%kb%k)%ka%k+b%kk(k(a%k+b%k)%k)%kk
面对取模问题,一定要考虑清楚边界情况,是否会超出界限!

讲了上面这么多,回到本题,需要找到(a + b + c) % k == 0,且a、b、c三数之和最大,我们可以求出每个数的余数,用一个数组存储相同余数的数,并且保证第一个数一直是最大的数,之后就可以遍历模k的余数,这里又有技巧,只需要遍历a、b两个数的余数即可,用上面的推导公式,可以把c的余数直接找到。要注意的是,a、b、c三个数可能余数都相同(也可能两两相同),所以,我们需要存储每个余数的前三大的数,在余数相同时就找同余数的次大的数。

import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int k = scan.nextInt();
		// 存储余数数组,只记录每个余数前三大的数
		int[][] rar = new int[k][3];
		for (int i = 0; i < n; i++) {
			int num = scan.nextInt();
			int r = num % k;
			if (num > rar[r][0]) {
				// 大于第一大的数,往后挪动位置
				rar[r][2] = rar[r][1];
				rar[r][1] = rar[r][0];
				rar[r][0] = num;
			} else if (num > rar[r][1]) {
				// 大于第二大的数
				rar[r][2] = rar[r][1];
				rar[r][1] = num;
			} else if (num > rar[r][2]){
				// 大于等于第三大的数
				rar[r][2] = num;
			}
		}
		int a = 0, b = 0, c = 0;
		long sum = 0;
		long ans = 0;
		for (int i = 0; i < k; i++) {
			for (int j = 0; j < k; j++) {
				// 遍历前两个数的余数
				int r = (k - (i + j) % k) % k;  //计算出第三个数的余数
				if (i == j) {
					// 前两个余数相同
					// a、b依次取第1、2大的数
					a = rar[i][0];
					b = rar[i][1];
					if (r == i) {
						// c的余数还是和前面相同
						c = rar[i][2];
					} else {
						// 不同,那就取当前余数最大的
						c = rar[r][0];
					}
				} else {
					// 前两个余数不同,各自取各自最大的
					a = rar[i][0];
					b = rar[j][0];
					if (r == i || r == j) {
						c = rar[i][1];
					} else {
						c = rar[r][0];
					}
				}
				// 更新答案
				sum = a + b + c;
				if (sum > ans) ans = sum;
			}
		}
		System.out.println(ans);
	}
}

这道题目做完真是神清气爽。

※十二、结账问题(贪心)

在这里插入图片描述
在这里插入图片描述
标准差是刻画样本数据在平均值的上下波动情况,首先,注意到标准差中需要计算平均值,需要支付的钱数是固定的,人数也是固定的,所以这部分值是固定的,在变的是每个人付的钱,我们要让每个人付的钱尽可能接近平均值。这又涉及这两个问题,一部分人钱不够平均值,一部分人钱>=平均值,对于钱不够的这部分人,没有办法,只能让他们全部把钱拿出来(不拿的话标准差更大),而这部分付完钱后,还剩余一部分钱,这部分钱又算平均值,由后面有钱的人来给。

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		double s = scan.nextDouble();
		int[] num = new int[n];
		double avg = 0;
		for (int i = 0; i < n; i++) {
			num[i] = scan.nextInt();
		}
		// 求出每个人需要支付的钱数的平均值
		avg = 1.0 * s / n;
		double sum = 0;
		double newAvg = avg;
		Arrays.sort(num);
		for (int i = 0; i < n; i++) {
			if (num[i] < newAvg) {
				// 小于平均数,必须全部交出来
				s -= num[i];
				// 计算新的每人需要支付的平均数
				newAvg = 1.0 * s / (n - i - 1);
				// 小于平均数时的标准差(正常算)标准差所需的均值是始终恒定的
				sum = sum + (avg - num[i]) * (avg - num[i]);
			} else {
				// 一旦当前值大于等于平均数,那么意味着后面的所有人都可以支付这个钱
				sum = sum + (avg - newAvg) * (avg - newAvg) * (n - i);
				break;
			}
		}
		System.out.printf("%.4f", Math.sqrt(sum / n));
	}
}

※十三、无聊的逗(状态搜索)

在这里插入图片描述
数据规模<=15,可以考虑搜索。本题的难点在于木棍并不是全部要用完,而是可以一部分用,一部分不用,所以跟普通的二进制状态搜索还不同,因为普通的二进制状态搜索只会由0、1,0放一起,1放一起,全部都用完。

但是本题,如何考虑木棍无法全部用完的问题?只需要遍历每一种状态即可,因为对给定的木棍,总共的状态就那几种,找完之后,遍历所有状态,只要两种状态没有重复选木棍!即可,也就是 i & j == 0,但是还不够呀,还要保证两个方案的木棍长度一致,欸!这样之后就可以看是否更新最大值了。

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		int n = scan.nextInt();
		int[] sticks = new int[n];
		for (int i = 0; i < n; i++) sticks[i] = scan.nextInt();
		// 状态搜索,排除掉全0、全1的情况
		int max = 0;
		int[] choices = new int[(int)(Math.pow(2, n))];
		for (int i = 1; i < (1 << n) - 1; i++) {
			for (int j = 0; j < n; j++) {
				if ((i &  (1 << j)) != 0) {
					// 把当前组合中为1的木棍加到当前方案中
					choices[i] += sticks[j];
				}
			}
		}
		// 遍历所有方案
		for (int i = 1; i < (1 << n) - 1; i++) {
			for (int j = 1; j < (1 << n) - 1; j ++) {
				// 相与=0说明两个选择没有重复选择木棍,并且可以存在部分木棍选择的方案
				// 还要保证木棍的 长度一致
				if ((i & j) == 0 && choices[i] == choices[j]) {
					max = Math.max(max, choices[i]);
				}
			}
		}
		System.out.println(max);
	}
}

休息下,明天继续冲!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@u@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值