一、礼物(前缀和 + 二分答案)
对石子个数进行二分,然后去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)%k,减法同理,a∗b%k=(a%k∗b%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=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
上面介绍了两个数的情况,下面来看看三个数的情况:
同
样
的
道
理
,
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=(k−a%k−b%k)%k,对吗?a%k+b%k肯定会超过k的范围所以应该处理为:(k−(a%k+b%k)%k)%k,这样就可以避免超过k范围的情况
面对取模问题,一定要考虑清楚边界情况,是否会超出界限!
讲了上面这么多,回到本题,需要找到(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);
}
}
休息下,明天继续冲!