XTUoj 1192 Ballon

文章介绍了在处理编程竞赛中涉及大量数据的交集问题时,如何通过离散化和双指针策略来优化解决方案。首先,由于数据范围较大,无法直接用数组表示,因此采用排序和去重创建一个压缩的数组。接着,使用二分查找将大范围的数值映射到小范围的数组下标,从而判断元素是否存在于两个集合中。最后,通过双指针方法直接找出两个有序数组的交集,避免了多余的查找操作,提高了效率。
摘要由CSDN通过智能技术生成

Balloon

Submit Code ] [ Top 20 Runs ] [ Runs Status ]
Acceteped : 706Submit : 2163
Time Limit : 1000 MSMemory Limit : 65536 KB

Description

题目描述

ICPC比赛当你过了一道题以后,会发一个标识这道题颜色的气球。 现给你2个队过的气球的颜色,求他们都过了的气球是哪些?

输入

第一行是一个整数K,表示样例的个数(K≤100)。 每个样例的第一行是两个整数N和M,1≤N,M≤100000,表示两个队分别过题的数量; 第二、三行分别是N和M个有序的整数Xi和Yi,0≤Xi,Yi≤10^9,表示气球的颜色。 输入数据保证集合{Xi}和{Yi}的元素都是唯一的,且两个集合一定存在交集。

输出

每个样例输出两行,第一行输出相同的过题数目S,第二行按升序输出S个整数,每个整数之间空一个空格。

样例输入

2
3 3
1 2 3
1 2 3
3 2
1 2 3
2 3

样例输出

3
1 2 3
2
2 3

方法1 -- 离散化

思路

  首先看数据范围【0≤Xi,Yi≤10^9】,所以想要用一个vis数组,把Xi、Yi作为下标来判断这个值是否出现过不可行,数组明显开不了那么大。

   可以看到:虽然【0≤Xi,Yi≤10^9】,但是数组的长度就小了几个数量级【1≤N,M≤100000】,这时候就可以利用离散化,把较大的值域区间压缩到较小的定义域区间内

 建立起了这种映射关系后,想要判断一个数是否出现过,那就只要判断它在较小区间内对应的值是否出现过即可,本题中的小区间【1≤N,M≤100000】,这个大小就可以借助数组来判断了

   那么怎么建立这种映射关系呢,最简单的就是把【目标数值】映射到【这个数值在整个数组中排第几大】,然后这个时候就可以用到二分查找了。

实现

  1. 将两个数组合并、排序,然后去重,得到一个新数组set;
  2. 对X数组中的所有值在set中进行二分查找,得到的下标idx就是这个值排第几大,建立一个vis数组,令vis[idx]=1;
  3. 这时候X数组中的值都标记过了,然后再对Y数组中的值也进行二分查找,看得到的下标在vis中是否被标记过,标记过就说明这个值也在X中,直接加入答案中。

代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 100005
int X[N] = {0};
int Y[N] = {0};
int set[N * 2] = {0};
int cmp(const void* a, const void* b) {
	return *((int *)a) - *((int *)b);
}
int binary_search(int x, int left, int right) {
	while (left <= right) {
		int mid = left + (right - left) / 2;
		if (set[mid] > x) {
			right = mid - 1;
		} else if (set[mid] < x) {
			left = mid + 1;
		} else {
			return mid;
		}
	} 
	return -1;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int m, n;
		int len = 0, idx = 0;
		scanf("%d %d", &m, &n);
		for (int i = 0; i < m; i++) {
			scanf("%d", &X[i]);
			set[idx++] = X[i];
		}
		for (int i = 0; i < n; i++) {
			scanf("%d", &Y[i]);
			set[idx++] = Y[i];
		}
		// 排序、去重 
		qsort(set, idx, sizeof(int), cmp);		
		for (int i = 0; i < idx; i++) {
			if (i == 0 || set[i] != set[i - 1]) {
				set[len++] = set[i];
			}
		}
		
		int cnt = 0;
		int ans[m + n] = {0};
		int vis[m + n] = {0};
		for (int i = 0; i < m; i++) {
			// 离散化,通过二分查找把较大的数据压缩到一个较小的区间内,就可以当成数组下标了 
			vis[binary_search(X[i], 0, len - 1)] = 1;
		}
		for (int i = 0; i < n; i++) {
			if (vis[binary_search(Y[i], 0, len - 1)] == 1) {
				ans[cnt++] = Y[i];
			}
		}
		printf("%d\n", cnt);
		for (int i = 0; i < cnt; i++) {
			printf("%d", ans[i]);
			printf(i == cnt - 1 ? "\n" : " ");		
		}
	}
}

优化

在得到set数组的时候,要对X、Y合并后的数组排序,时间复杂度为O((m+n)\log(m+n) ),但是注意到在题目中,X、Y数组都是有序的,所以就可以利用以下的方法得到set

 贴一个链接,方便理解合并的步骤如何快速合并两个有序数组? - 知乎 (zhihu.com)

设置两个数 i1=0、i2=0,然后分以下情况讨论:

- X[i1] < Y[i2]: 把X[i1]填入set数组中,i1++

- X[i1] > Y[i2]: 把Y[i2]填入set数组中,i2++

- X[i1] == Y[i2]: 任选一个填入set数组,i1、i2同时加一

- 若是i1等于数组X的长度后,将Y数组i2以后的值依次填入set,反之将X数组剩余的数填入 

这样就充分利用了题目所给的条件,时间复杂度为O(m+n),代码如下:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 100005
int X[N] = {0};
int Y[N] = {0};
int set[N * 2] = {0};
int binary_search(int x, int left, int right) {
	while (left <= right) {
		int mid = left + (right - left) / 2;
		if (set[mid] > x) {
			right = mid - 1;
		} else if (set[mid] < x) {
			left = mid + 1;
		} else {
			return mid;
		}
	} 
	return 0;
}
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int m, n;
		int len = 0, idx = 0;
		scanf("%d %d", &m, &n);
		for (int i = 0; i < m; i++) {
			scanf("%d", &X[i]);
		}
		for (int i = 0; i < n; i++) {
			scanf("%d", &Y[i]);
		}
		int i1 = 0, i2 = 0;
		while (i1 < m && i2 < n) {
			if (X[i1] < Y[i2]) {
				set[idx++] = X[i1];
				i1++;
			} else if (X[i1] > Y[i2]) {
				set[idx++] = Y[i2];
				i2++;
			} else {
				set[idx++] = X[i1]; // 或Y[i2];
				i1++, i2++; 
			}
		}
		while (i1 < m) {
			set[idx++] = X[i1++];
		}
		while (i2 < n) {
			set[idx++] = Y[i2++];
		}

		int cnt = 0;
		int ans[m + n] = {0};
		int vis[m + n] = {0};
		for (int i = 0; i < m; i++) {
			// 离散化,通过二分查找把较大的数据压缩到一个较小的区间内,就可以当成数组下标了 
			vis[binary_search(X[i], 0, idx - 1)] = 1;
		}
		for (int i = 0; i < n; i++) {
			if (vis[binary_search(Y[i], 0, idx - 1)] == 1) {
				ans[cnt++] = Y[i];
			}
		}
		printf("%d\n", cnt);
		for (int i = 0; i < cnt; i++) {
			printf("%d", ans[i]);
			printf(i == cnt - 1 ? "\n" : " ");		
		}
	}
}

方法2 -- 直接利用双指针

其实在上一种做法的优化中,可以看出因为两个数组有序,在不断地比较X[i1]和Y[i2]这个过程中,就可以直接找出在两个数组中都存在的数,也就是在X[i1] == Y[i2]的时候,这时直接把X[i1]或Y[i2]加入到答案中就行了

代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 100005
int X[N] = {0};
int Y[N] = {0};
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int m, n;
		scanf("%d %d", &m, &n);	
		for (int i = 0; i < m; i++) {
			scanf("%d", &X[i]);
		}
		for (int i = 0; i < n; i++) {
			scanf("%d", &Y[i]);
		}
		int i1 = 0, i2 = 0;
		int cnt = 0, ans[m + n] = {0};
		while (i1 < m && i2 < n) {
			if (X[i1] < Y[i2]) {
				i1++;
			} else if (X[i1] > Y[i2]) {
				i2++;
			} else {
				ans[cnt++] = X[i1];
				i1++, i2++;
			}
		}
		printf("%d\n", cnt);
        for (int i = 0; i < cnt; i++) {
            printf("%d", ans[i]);
            printf(i == cnt - 1 ? "\n" : " ");        
        }
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值