蓝桥杯压缩变换

题目描述

小明最近在研究压缩算法。

他知道,压缩的时候如果能够使得数值很小,就能通过熵编码得到较高的压缩比。
然而,要使数值很小是一个挑战。

最近,小明需要压缩一些正整数的序列,这些序列的特点是,后面出现的数字很大可能是刚出现过不久的数字。对于这种特殊的序列,小明准备对序列做一个变换来减小数字的值。

变换的过程如下:
从左到右枚举序列,每枚举到一个数字,如果这个数字没有出现过,刚将数字变换成它的相反数,如果数字出现过,则看它在原序列中最后的一次出现后面(且在当前数前面)出现了几种数字,用这个种类数替换原来的数字。

比如,序列(a1,a2,a3,a4,a5)=(1,2,2,1,2)在变换过程为:
a1:1未出现过,所以a1变为-1;
a2:2未出现过,所以a2变为-2;
a3:2出现过,最后一次为原序列的a2,在a2后,a3前有0种数字,所以a3变为0;
a4:1出现过,最后一次为原序列的a1,在a1后,a4前有1种数字,所以a4变为1;
a5:2出现过,最后一次为原序列的a3,在a3后,a5前有1种数字,所以a5变为1.
现在,给出原序列,请问,按这种变换规则变换后的序列是什么。

输入格式:
输入第一行包含一个整数N,序列表示的长度
第二行所有游戏N个正整数,表示输入序列。

输出格式:
输出一行,包含n个数,表示变换后的序列。

例如,输入:
5
1 2 2 1 2

程序应该输出:
-1 -2 0 1 1

再例如,输入:
12
1 1 2 3 2 3 1 2 2 2 3 1

程序应该输出:
-1 0 -2 -3 1 1 2 2 0 0 2 2

数据规模与约定
对于30%的数据,n <= 1000;
对于50%的数据,n <= 30000;
对于100%的数据,1 <= n <= 100000,1 <= ai <= 10 ^ 9

资源约定:
峰值内存消耗(含虚拟机)<256M
CPU消耗<3000ms

java代码如下

对于每一个数字,判断是否第一次遇到,如果是第一次遇到,将其加入map中,map的value为该数最后一次遇到的下标;如果不是第一次遇到,统计上一次遇到到该次遇到数字中间有多少种不同的数字。

import java.util.*;

public class Main {
	static Map<Integer, Integer> map = new HashMap<Integer, Integer>();
	static int N;
	static int[] arr;
	static int[] ans;

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		N = sc.nextInt();
		arr = new int[N];
		ans = new int[N];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = sc.nextInt();
			// 如果是第一次发现该数
			if (map.get(arr[i]) == null) {
				ans[i] = -arr[i];
			}
			// 如果不是第一次发现该数
			else {
				// 根据最后一次发现该数的下标,统计到当前下标出现数字的种类
				// 使用set防止重复数字,可以统计有多少种不同的数
				Set<Integer> set = new HashSet<Integer>();
				for (int j = map.get(arr[i]) + 1; j < i; j++) {
					set.add(arr[j]);
				}
				ans[i] = set.size();
			}
			// 无论该key值是否第一次出现,将其put入map中
			map.put(arr[i], i);
		}
		for (int i = 0; i < ans.length; i++) {
			System.out.print(ans[i] + " ");
		}
		sc.close();
	}
}

代码优化

如上代码中,如果不是第一次发现某数,则需要将从上一次出现到该次出现所有的数进行遍历,时间复杂度为n。
能不能将此处优化为O(log n)呢?
这里用到了区间树的概念。

import java.util.*;

public class Main {
	static Map<Integer, Integer> map = new HashMap<Integer, Integer>();
	static int N;
	static int[] a; // 记录原始数据
	static int[] ans; // 记录变换后的结果
	static int[] b; // 这是一个0,1序列,b[i]为1当且仅当a[i]代表的数字最后一次出现的位置为i
					// 最后求区间和的时候只要将b对应下标的值加起来即可
	static SegTree root; // 区间树

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		N = sc.nextInt();
		a = new int[N];
		b = new int[N];
		ans = new int[N];
		root = buildSegTree(0, N - 1); // 构造区间树

		for (int i = 0; i < a.length; i++) {
			int num = sc.nextInt();
			a[i] = num; // 将原数据保存起来
			Integer preIndex = map.get(num);
			// 如果是第一次发现该数
			if (preIndex == null) {
				ans[i] = -num;
				b[i] = 1;
			}
			// 如果不是第一次发现该数
			else {
				// 计算map.get(a[i])+1到i-1之间不同数字的个数==>求区间和
				ans[i] = query(root, preIndex + 1, i - 1);
				b[preIndex] = 0; // 更新上次出现位置为0
				b[i] = 1; // 更新本次出现位置为1
				// 更新之前位置的区间树
				update(root, preIndex, -1);
			}
			// 无论该key值是否第一次出现,将其put入map中
			map.put(a[i], i);
			// 更新i位置的区间树
			update(root, i, 1);
		}
		for (int i = 0; i < ans.length; i++) {
			System.out.print(ans[i] + " ");
		}
		sc.close();
	}

	// 计算b[p1]到b[p2]的和,通过区间树可以将其优化为log n的时间复杂度
	static int query(SegTree tree, int p1, int p2) {
		int l = tree.l;
		int r = tree.r;

		if (p1 <= l && p2 >= r) {
			return tree.sum;
		}
		if (p1 > p2) {
			return 0;
		}
		int mid = (l + r) / 2;
		int res = 0;
		if (p1 <= mid) {
			res += query(tree.lSon, p1, p2);
		}
		if (p2 > mid) {
			res += query(tree.rSon, p1, p2);
		}
		return res;
	}

	/**
	 * b发生变化后,需要更新区间树
	 * 
	 * @param p
	 *            改变的b的下标
	 * @param num
	 *            增量
	 */
	static void update(SegTree tree, int p, int num) {
		// 递归出口
		if (tree == null) {
			return;
		}
		// 处理的当前SegTree
		tree.sum += num;
		// 递归处理子问题
		int l = tree.l;
		int r = tree.r;
		int mid = (l + r) >> 1;
		if (p <= mid) { // 左区间应该改变
			update(tree.lSon, p, num);
		} else {
			update(tree.rSon, p, num);
		}
	}

	// 区间树的数据结构
	static class SegTree {
		int l, r; // 左右区间的下标
		int sum; // 区间内的和
		SegTree lSon; // 左子树
		SegTree rSon;// 右子树

		public SegTree(int l, int r) {
			this.l = l;
			this.r = r;
		}
	}

	// 构建区间树
	static SegTree buildSegTree(int l, int r) {
		SegTree segTree = new SegTree(l, r);
		// 左右区间相同,叶子结点,sum为b[l]
		if (l == r) {
			segTree.sum = b[l];
			return segTree;
		}
		// 左右区间不同
		int mid = (l + r) / 2;
		SegTree lson = buildSegTree(l, mid);
		SegTree rson = buildSegTree(mid + 1, r);
		segTree.lSon = lson;
		segTree.rSon = rson;
		segTree.sum = lson.sum + rson.sum;
		return segTree;
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值