Algo194 算法训练 审美课
题目如下:
这道题如果直接暴力是完全不可能的,最大范围是 n <= 50000 , m <= 20
这道题如果是纯暴力的话 应该是(1 + 2 … + n ) * m 的一个复杂度,最多会到好几亿的,但只给你1秒,所以不可能暴力的
这道题的算法思想我也不知道是啥,我简单说下我的思路。
首先从这m入手,怎么能把这m变成1呢,变成1就只剩(1 + 2 … + n),就算n为50000也可以1秒跑完。
把m变成1就是一次就能比出两个同学是否完全相反,那就要把m个画变成一个东西,一个独一无二的东西。
看着例子 1 0 ,0 1 ,1 0 这不就是个二进制的01串吗,完全可以将这一串转换成一个十进制数。这样不仅保证唯一,也能一次比完。最大也就是2的20次,一百多万而已,int足以
下面的代码是第一次的写法,随便把(1 + 2 … + n )优化成n了,因为(1 + 2 … + n)最后两组超时了
package algo;
import java.util.Scanner;
/**
* @Description: 算法训练 审美课
* @ClassName: Algo194
* @author: fan.yang
* @date: 2020/07/16 11:51
*/
public class Algo194 {
/**
* 刚开始这样写的 90分 最后一组超内存了 可惜 这里我当时是认为数组写太多占内存了 750ms 300mb
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[] array1 = new int[m];
int[] judgeArray = new int[1024 * 1024];
int k = 1;
//1 2 4 8... 要转化成十进制 所以提前准备好每位的数
for(int i = 0;i < m;i++){
array1[i] = k;
k *= 2;
}
//这个二维数组是这个意思 n可以看出这是每个学生都有的,每个学生可以存两个数
//第一个数是他自己那个01串转的十进制,第二个就是完全跟他相反的01串转的十进制,方便最后的判断
int[][] array2 = new int[n][2];
for(int i = 0;i < n;i++){
int sum1 = 0;
int sum2 = 0;
for(int j = 0;j < m;j++){
//如果是1就是给他自己那个十进制加上去 反之就给相反的加
if(scanner.nextInt() == 1){
sum1 += array1[j];
}else{
sum2 += array1[j];
}
}
array2[i][0] = sum1;
//这个就是统计转成十进制后都是sum1学生的数量 往前推就是他们01串是一模一样的
//这也是为了方便后面的统计
judgeArray[sum1]++;
array2[i][1] = sum2;
}
int result = 0;
//这里优化了下一个循环结束
for(int i = 0;i < n;i++){
//加上与某个同学完全相反的学生数量
result += judgeArray[array2[i][1]];
//加上后要把这个同学所在的那个数量减1 就代表这个学生已经比过了 不然跟他完全相反的同学来比对就会多一个 重复了
judgeArray[array2[i][0]]--;
}
System.out.println(result);
}
}
/*
现在用例子来说明
比如
3 2
1 0
0 1
1 0
同学1 是 1 0 字串 转化为十进制就是 1 * 1 + 0 * 2 = 1 其相反值为 0 * 1 + 1 * 2 = 2
同学2 是 0 1 字串 转化为十进制就是 0 * 1 + 1 * 2 = 2 其相反值为 1 * 1 + 0 * 2 = 1
同学3 是 1 0 字串 转化为十进制就是 1 * 1 + 0 * 2 = 1 其相反值为 0 * 1 + 1 * 2 = 2
这时候 judgeArray[1] = 2 judgeArray[2] = 1
最后循环统计
同学1 开始统计 发现 judgeArray[2] = 1 所以 rusult + 1 = 1 因为同学1统计过了 所以judgeArray[1]--
同学2 开始统计 发现 judgeArray[1] = 1 所以 rusult + 1 = 2 因为同学2统计过了 所以judgeArray[2]--
同学3 开始统计 发现 judgeArray[2] = 0 所以 rusult + 0 = 2 因为同学3统计过了 所以judgeArray[1]--
最后结果就是 2 就是这样一个过程
*/
好了 问题就来了 我个人认为上面这个算法还可以了,果然时间是够了 700ms就结束了 但内存却超了
我当时认为是数组多了,所以我尝试优化了一波,这里的优化就是去掉了array2数组,不需要去知道每个学生其自己的数以及与其相反的数,只需要知道每个数的学生有多少。因为我仔细想了下,自己的数加上完全相反的数得到的其实是全是1的二进制串转的十进制,比如01和10转成十进制分别是2和1(我是从左开始),相加不刚好等于11转的3吗。这样只要知道某个数就一定知道其相反的数,同时这个两个数所有的同学数量相乘就是所要的对数
package algo;
import java.util.Scanner;
/**
* @Description: 算法训练 审美课
* @ClassName: Algo194
* @author: fan.yang
* @date: 2020/07/16 11:51
*/
public class Algo194 {
/**
* 第二种写法 其实就是优化了一下算法 把那个二维数组去掉了 但发现内存还是跟第一种差不多 90分 fuck 750ms 300mb
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[] array1 = new int[m];
int[] judgeArray = new int[1024 * 1024];
int k = 1;
int num = 0;
//1 2 4 8...
for(int i = 0;i < m;i++){
num += k;
array1[i] = k;
k *= 2;
}
for(int i = 0;i < n;i++){
int sum = 0;
for(int j = 0;j < m;j++){
if(scanner.nextInt() == 1){
sum += array1[j];
}
}
//这里优化了一波 直接知道每个数的学生数量即可
judgeArray[sum]++;
}
int result = 0;
for(int i = 0;i <= num / 2;i++){
//然后知道了 数 + 相反数 = num ,并且两个数的数量相差就是对数
result += judgeArray[i] * judgeArray[num - i];
}
System.out.println(result);
}
}
/*
例子如下
8 2
1 0
0 1
1 0
1 1
0 0
1 0
0 1
0 0
可以得出0的学生数为2,1的学生数为3,2的学生数为2,3的学生数为1 num = 3 (这个3是这样来的 m=2 所以就两位2进制 最大值就是 1 * 2 + 1 * 1)
所以就算 0 和 3 的学生数相乘 2 * 1 = 2 加上 1 和 2的学生数学生数相乘 3 * 2 = 6 结果就是8
*/
结果悲剧了,内存还是超了 fuck,那肯定不是我算法的问题了,我的算法效率已经ok,那唯一的问题出在了scanner,我是把scanner放在两层循环里,这样在最后一组数据里,会有50000 * 20次输入。怪不得爆掉
package algo;
import java.util.Scanner;
/**
* @Description: 算法训练 审美课
* @ClassName: Algo194
* @author: fan.yang
* @date: 2020/07/16 11:51
*/
public class Algo194 {
/**
* 第三种 考虑到应该不是数组太多或者操作太多 那就是scanner的问题了 最后一组预计是50000 * 20 1000000次输入估计
* 内存就爆掉了 所以决定每个学生只输入一次 按行取 比如最后一组我省了950000次的输入
* 562ms 127mb 虽然用到大量的String 但相比Scanner还是少了
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
int[] array1 = new int[m];
int[] judgeArray = new int[1024 * 1024];
int k = 1;
int num = 0;
//1 2 4 8...
for(int i = 0;i < m;i++){
num += k;
array1[i] = k;
k *= 2;
}
//这个要注意
scanner.nextLine();
for(int i = 0;i < n;i++){
int sum = 0;
String str = scanner.nextLine();
String[] strArray = str.split(" ");
for(int j = 0;j < strArray.length;j++){
if("1".equals(strArray[j])){
sum += array1[j];
}
}
judgeArray[sum]++;
}
int result = 0;
for(int i = 0;i <= num / 2;i++){
result += judgeArray[i] * judgeArray[num - i];
}
System.out.println(result);
}
}