CCF认证 csp第15次 第3题 CIDR合并 (java 100分)

开始搜的题解是90分的,因为对java不熟悉,就从里面学了很多东西,indexOf,split,还有处理合并的方法。

最后一个点过不了,我开始我的优化之路。

题解的排序是插入排序,这样是O(n*n*log(n)),我用sort,就可以降到O(n*log(n))。

在合并的过程中,题解的方法模拟的时候用remove操作,这样加上本来需要O(n)的扫描时间,就是O(n*n)复杂度。

把它优化成O(n)。对于这两个合并,我都采用了扫描过程中把需要的点留下来的思路,这样不用花多余的时间去把元素从list里面删除,O(n)扫一遍就好。第二个合并因为还要和之前的比较,我就用了stack,每次用栈顶的元素和待比较的位置进行比较,如果要合并就出栈,把新的结果放在compare里面,空栈时把结果放进来,compare多加一个1。这样可以达到复杂度和比较次数相同的目的。

这样最高的复杂度是排序的时候,需要O(nlog(n)),应该能过,但还是90分。我就被卡住了很久很久。

但是后来俱乐部xzq学长告诉我java的Scanner是很慢的,像c++的cin一样,大数据量需要优化输入输出的。去找了一下优化输入输出的方法,最后过了。无比振奋啊,这就是全网首发的csp15-3 java实现满分题解(蒟蒻的自嗨)!

最开始了解写法和思路的博客,我找的题解https://blog.csdn.net/qq_36143109/article/details/88411854#commentBox

学习加速输入的博客https://www.jianshu.com/p/1184545102c7

 


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


class prefix implements Comparable<prefix>{
	public int[] ip;
	public int len;
	public prefix(int[] ip,int len) {
		this.ip = ip;
		this.len = len;
	}
	public int compareTo(prefix x) {
		for(int i = 0; i < 4; i++) {
			if(this.ip[i] != x.ip[i])
				return this.ip[i] - x.ip[i];
		}
		return this.len - x.len;
	}
}
 

class Reader {
    static BufferedReader reader;
    static StringTokenizer tokenizer;

    /**
     * call this method to initialize reader for InputStream
     */
    static void init(InputStream input) {
        reader = new BufferedReader(
                new InputStreamReader(input));
        tokenizer = new StringTokenizer("");
    }

    /**
     * get next word
     */
    static String next() throws IOException {
        while (!tokenizer.hasMoreTokens()) {
            //TODO add check for eof if necessary
            tokenizer = new StringTokenizer(
                    reader.readLine());
        }
        return tokenizer.nextToken();
    }

    static int nextInt() throws IOException {
        return Integer.parseInt(next());
    }

    static double nextDouble() throws IOException {
        return Double.parseDouble(next());
    }
}

public class Main {
 
	public static void main(String[] args) throws IOException {
		
//		Scanner in = new Scanner(System.in);
		
//		int n = Integer.parseInt(in.nextLine());
		Reader.init(System.in);
		int n = Reader.nextInt();
		
		List<prefix> list = new ArrayList<prefix>(); 
		
		
		//读入并转换成prefix类
		for(int d = 0;d<n;d++) {
			
//			String ip = in.nextLine();
			String ip = Reader.next();
			//构建prefix对象
			int[] Ip = new int[4];
			int len = 0;   
			int index = ip.indexOf("/");
			if(index != -1) {
				String[] b =  ip.split("/");
				len = Integer.parseInt(b[1]);
				String str = b[0];
				if(str.indexOf(".") != -1) b = str.split("\\.");
				else b = new String[] {str};
				//同时处理了省略后缀型和标准型
				for(int i = 0; i < b.length; i++)  Ip[i] = Integer.parseInt(b[i]);
				for(int i = b.length; i<4 ;i++)  Ip[i] = 0;
			}else {//省略长度型
				String[] b =  new String[]{ip};
				if(ip.indexOf(".") != -1) b = ip.split("\\.");
				len = b.length * 8;
				for(int i = 0;i < b.length;i++)  Ip[i] = Integer.parseInt(b[i]);
				for(int i = b.length; i < 4; i++)  Ip[i] = 0;
			}
			prefix prefix = new prefix(Ip,len);
			list.add(prefix);
		}
		
		Collections.sort(list);
		
		List<prefix> list1 = new ArrayList<prefix>();
		
		//从小到大合并 从list找到合并后的元素放到list1里面
		int size = list.size() - 1;
		int min;
		int[] ip1, ip2;
		
		//k和k + 1匹配,能匹配则删掉k + 1,继续和新的k + 1匹配
		int k = 0, nxt;//nxt指向第一个不能和k合并的值
		while(k <= size) {
			list1.add(list.get(k));
			nxt = k + 1;
			if(nxt > size) break;
			while(nxt <= size) {
				min = Math.min(list.get(k).len, list.get(nxt).len);
				ip1 = and(list.get(k).ip,min);
				ip2 = and(list.get(nxt).ip,min);
				//取更小的长度min min之后的部分截掉变成0, 如果计算结果相同说明它们的匹配集相同
				if(cmp(ip1, ip2) == 0) 
					nxt++;
				else break;				
			}
			k = nxt;
		}
		
		
		list.clear();
		size = list1.size() - 1;
		for(int i = 0; i <= size; i++){
			list.add(list1.get(i));
		}

		//同级合并,永远从栈顶开始找
		Stack<prefix> list2 = new Stack<prefix>();
		size = list.size();
		int compare = 1;
		list2.push(list.get(0));
		while(compare < size) {
			prefix from = list2.pop();
			prefix get = can_combine2(from, list.get(compare));
			if(get == from) {
				list2.push(from);
				list2.push(list.get(compare));
				compare++;
			}
			else {
				list.set(compare, get);
				if(list2.empty()) {
					list2.push(list.get(compare));
					compare++;
				}
			}
		}
		
		size = list2.size();
		for(int i = 0; i < size; i++) list1.set(i, list2.pop());
		list.clear();
		for(int i = size - 1; i >= 0; i--) list.add(list1.get(i));
		
//		in.close();
			
		StringBuilder sb = new StringBuilder();
		for(prefix show:list) {
			sb.append(show.ip[0]+"."+show.ip[1]+
					"."+show.ip[2]+"."+show.ip[3]+"/"+show.len + "\n");
			
		}
		sb.append(" ");
		System.out.print(sb);
//		for(prefix show:list) {
//			System.out.println(show.ip[0]+"."+show.ip[1]+
//					"."+show.ip[2]+"."+show.ip[3]+"/"+show.len);
//		}
		
		
	}
	
	public static prefix can_combine2(prefix a, prefix b) {
		//如果不能合并,返回a
		if(a.len != b.len) return a;
		int len = a.len - 1;
		int[] ip1 = and(a.ip, len);
		int[] ip2 = and(b.ip, len);
		if(cmp(ip1, ip2) == 0) return new prefix(ip1, len);
		else return a;
	}
	
	public static int cmp(int[] x, int[] y) {
		for(int i = 0; i < 4; i++) {
			if(x[i] > y[i])
				return 1;
			if(x[i] < y[i])
				return -1;
		}
		return 0;
	}
	
	public static int cmp1(prefix a, prefix b) {
		//判断a是否比b大, a > b返回1, a < b返回-1,a == b返回0
		int[] x = a.ip;
		int[] y = b.ip;
		for(int i = 0;i < 4;i++) {
			if(x[i] > y[i])
				return 1;
			else if(x[i] < y[i])
				return -1;
		}
		if(a.len < b.len) return -1;
		else if(a.len == b.len) return 0;
		else return 1;
	}
	
	//获得一个前len位和p一样,后面为0的ip地址,用长度为4的数组表示
	public static int[] and(int[] p, int len) {
		int n = len / 8;
		int m = len % 8;
		int[] P = new int[4];
		for(int i = 0; i < n; i++) P[i] = p[i];
		//-1的二进制位全部是1,左移8-m位刚好使末尾8-m位是0
		if(n<4) P[n] = p[n] & (-1 << (8 - m));
		for(int i = n + 1; i < 4; i++) {
			P[i] = 0;
		}
		return P;
	}
}

 

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值