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

本文分享了一种针对CSP15-3问题的Java实现优化策略,通过改进排序算法和合并过程,将复杂度从O(n*n*log(n))降低到O(n*log(n)),并介绍了加速输入输出的方法,最终实现了Java版的满分题解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开始搜的题解是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.youkuaiyun.com/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;
	}
}

 

### CCF-CSP35认证第三析与解法 关于CCF-CSP35认证中的第三,目前公开的具体目描述尚未广泛传播于网络资源中[^1]。然而,基于以往CSP认证的特点以及考生反馈的信息,可以推测该类目通常考察的是编程基础能力和逻辑思维能力[^2]。 #### 可能涉及的知识点 此类目往往围绕以下几个方面展开: - **数据结构**:如数组、链表、栈、队列等基本操作。 - **算法设计**:包括但不限于贪心算法、动态规划、二查找等常见方法。 - **字符串处理**:对于输入输出格式的要求较高,需注意边界条件和特殊情况下的行为。 下面给出一个假设性的例子来说明如何解答类似的竞赛问: 假定本是一个简单的区间覆盖或者路径优化问,则解决方案如下所示: ```cpp #include <bits/stdc++.++.h> using namespace std; int main(){ int n; cin >> n; // 输入n表示有多少个区域或节点 vector<pair<int, int>> intervals(n); for(auto &p : intervals){ cin >> p.first >> p.second; // 每组两个数代表起点终点坐标 } sort(intervals.begin(), intervals.end()); long long res = 0; int last_end = INT_MIN; for(const auto &[start,end]:intervals){ if(start>last_end){ res += end-start; last_end=end; } else{ if(end>=last_end){ res+=end-last_end; last_end=end; } } } cout<<res<<endl;// 输出最终结果即总长度或其他目标函数值 } ``` 上述代码片段展示了当面对连续区间的合并计算时的一种通用策略——通过排序并遍历所有给定区间实现最优求解过程[^3]。 #### 注意事项 在参加正式比赛前务必熟悉评测环境配置参数限制情况(时间复杂度O()要求),同时也要关注细节部比如是否有负权边存在等问影响到具体实现方式的选择上。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值