您的位置:首页 > 其它

蓝桥杯2016年压缩变换

2020-07-14 06:35 302 查看

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

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

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

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

比如,序列(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和。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: