蓝桥杯2016年压缩变换

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

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

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

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

比如,序列(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,序列表示的长度
第二行所有游戏Ñ个正整数,表示输入序列。

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

例如,输入:
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

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class 压缩变换 {
	static Map <Integer,Integer> lastIndex =new HashMap<Integer,Integer>();
	static int []b;
	static SegTree root;
	static int n;
	static int[] data;
	static int[] ans;
	public static void main(String arg[]) {
		Scanner sc=new Scanner(System.in);
		n=sc.nextInt();
		data=new int[n];
		ans=new int[n];//存放答案
		b= new int [n];//存放0 1
		root=buildSegTree(0,n-1);//初始化树

		for(int i=0;i<n;i++) {
			int num=sc.nextInt();
			data[i]=num;
			//如果没出现过,则变为-1;
			if(lastIndex.get(num)==null) {
			ans[i]=-num;	
			b[i]=1;
			update(root,i,1);
			}
			else {
			int index=lastIndex.get(num);
			ans[i]=query(root,index+1,i-1);
			b[index]=0;
			b[i]=1;
			update(root,index,-1);	//原来的出现的区间和减1
			update(root,i,1);	//现在的下标的区间和加1
			}
			
			lastIndex.put(num,i);

		}
		
		for(int j=0;j<n;j++) {
			System.out.print(ans[j]+" ");
		}
		
		
		
	}
	
	
//查询区间和是多少
	private static int query(SegTree tree,int l,int r) {
		
		if(l<=tree.l&&r>=tree.r) {
			return tree.sum;
		}
		int mid=(tree.l+tree.r)/2;
		int ans=0;
		if(l<=mid) {
		ans+=query(tree.lson,l,r);
		}
		if(r>mid) {
			ans+=query(tree.rson,l,r);
		}
		return ans;
	}
	
	//初始化树状数组
	private static SegTree buildSegTree(int l,int r) {
		SegTree tree=new SegTree(l,r);
		
		if(l==r) {
			tree.sum=b[l];
			return tree;
		}
		
		int mid=(l+r)/2;
		
		tree.lson=buildSegTree(l,mid);
		tree.rson=buildSegTree(mid+1,r);
		tree.sum=tree.lson.sum+tree.rson.sum;
		return tree;
		
	}
	
	//每次更新b之后,相应的更新tree
	private static void update(SegTree tree,int p,int i) {
		if(tree==null)
			return;
		
		tree.sum+=i;
		int l=tree.l;
		int r=tree.r;
		
		int mid=(l+r)/2;
		
		if(p<=mid) {
		
			update(tree.lson,p,i);
		
		}
		else if(p>mid) {
			
			update(tree.rson,p,i);
			
		}
		
	}
	static class SegTree {
		int l,r;//左右区间
		int sum;//和
		SegTree lson;//左子树
		SegTree rson;//右子树
		public SegTree(int l,int r) {
			this.l=l;
			this.r=r;
		}
   }
	
	
	
	
	

}

思路
这里的优化性能的方法是树状数组
并且将检查种类的问题转换为求两个索引之间区间和的问题。
首先第一次出现一个数字之后,数组b会将这个数字对应的下标里面的值设为1,当再次出现的时候,将再次出现的的设为1,之前的改为0,这样如果求两个索引之间的数字的种类数时,只需对两个索引之间的区间进行求和。
树状数组最初初始化时与b中的元素保持一致,它保存的是在不同区间(0,n-1)(二分区间将树伸展向下)的和,当b改变时,树状数组也进行update函数进行更新数值。
lastIndex是一个map,它的key是数字,value是这个数字上一次出现的下标。每次要及时更新数字的最近的上一次出现的下标位置。

注意在查询区间和的时候,在对左右子树进行递归查询的时候,参数左右区间的下标可以一直是最初的l和r,虽然这样看起来区间超过,但是并不影响出口的判断,大于当前树的区间时会返回当前树的sum和。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值