HDOJ 6703 Array
HDOJ 6703 Array
题目
array
*Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 3577 Accepted Submission(s): 1323
*Problem Description
You are given an array a1,a2,...,an(∀i∈[1,n],1≤ai≤n). Initially, each element of the array is unique.
Moreover, there are m instructions.
Each instruction is in one of the following two formats:
\1. (1,pos),indicating to change the value of apos to apos+10,000,000;
\2. (2,r,k),indicating to ask the minimum value which is not equal to any ai ( 1≤i≤r ) and **not less ** than k.Please print all results of the instructions in format 2.
Input
The first line of the input contains an integer T(1≤T≤10), denoting the number of test cases.
In each test case, there are two integers n(1≤n≤100,000),m(1≤m≤100,000) in the first line, denoting the size of array a and the number of instructions.
In the second line, there are n distinct integers a1,a2,...,an (∀i∈[1,n],1≤ai≤n),denoting the array.
For the following m lines, each line is of format (1,t1) or (2,t2,t3).
The parameters of each instruction are generated by such way :For instructions in format 1 , we defined pos=t1⊕LastAns . (It is promised that 1≤pos≤n)
For instructions in format 2 , we defined r=t2⊕LastAns,k=t3⊕LastAns. (It is promised that 1≤r≤n,1≤k≤n )
(Note that ⊕ means the bitwise XOR operator. )
Before the first instruction of each test case, LastAns is equal to 0 .After each instruction in format 2, LastAns will be changed to the result of that instruction.
(∑n≤510,000,∑m≤510,000 )
Output
For each instruction in format 2, output the answer in one line.
Sample Input
3 5 9 4 3 1 2 5 2 1 1 2 2 2 2 6 7 2 1 3 2 6 3 2 0 4 1 5 2 3 7 2 4 3 10 6 1 2 4 6 3 5 9 10 7 8 2 7 2 1 2 2 0 5 2 11 10 1 3 2 3 2 10 10 9 7 5 3 4 10 6 2 1 8 1 10 2 8 9 1 12 2 15 15 1 12 2 1 3 1 9 1 12 2 2 2 1 9
Sample Output
1 5 2 2 5 6 1 6 7 3 11 10 11 4 8 11
Hint
note:
After the generation procedure ,the instructions of the first test case are :
2 1 1, in format 2 and r=1 , k=1
2 3 3, in format 2 and r=3 , k=3
2 3 2, in format 2 and r=3 , k=2
2 3 1, in format 2 and r=3 , k=1
2 4 1, in format 2 and r=4 , k=1
2 5 1, in format 2 and r=5 , k=1
1 3 , in format 1 and pos=3
2 5 1, in format 2 and r=5 , k=1
2 5 2, in format 2 and r=5 , k=2the instructions of the second test case are :
2 7 2, in format 2 and r=7 , k=2
1 5 , in format 1 and pos=5
2 7 2, in format 2 and r=7 , k=2
2 8 9, in format 2 and r=8 , k=9
1 8 , in format 1 and pos=8
2 8 9, in format 2 and r=8 , k=9the instructions of the third test case are :
1 10 , in format 1 and pos=10
2 8 9 , in format 2 and r=8 , k=9
1 7 , in format 1 and pos=7
2 4 4 , in format 2 and r=4 , k=4
1 8 , in format 1 and pos=8
2 5 7 , in format 2 and r=5 , k=7
1 1 , in format 1 and pos=1
1 4 , in format 1 and pos=4
2 10 10, in format 2 and r=10 , k=10
1 2 , in format 1 and pos=2Source
分析
大概意思是给定一个数组, 有两个操作.
-
OP1: 把数组中的某个位置上的数加上一个很大的值.
-
OP2: 问数组中和前r个数都不一样, 但大于等于k的最小的数是什么.
这是一道非常典型的, 难度不大, 但有非常明显的人工埋坑痕迹的题目.
-
第一坑 数据范围
先看数组a的数据的范围 $ a \in [1,n] $ 且另有限制条件数组\(a\)中每一个数唯一, 这隐性表示了\(a\)是1到n的一个排列 .
再看修改和查询两个操作的数值范围, 也都被限制在了 \([1,n]\) 之间. 也就是说, 所有的操作都会是在\(1-n\)上进行的.
这些细节暗示了我们可以将这个问题进行简化.
-
第二坑 r/k动态计算
这里的\(r\)和\(k\)不是直接获得的, 而会根据上一个输出的答案进行异或\(XOR\)得到的.
一般这么设计的目的都是为了限制使用离线算法, 但这题这么设计的目的其实是想让计算过程变的很不直观, 让你琢磨不透到底进行的是什么操作.
尽管\(r\)和\(k\)都需要经\(XOR\)处理后才能得到, 但题目中还是特意让处理后的\(r\)和\(k\)都落在\([1,n]\)之间, 说明\([1,n]\)这个限制一定非常重要, 解题的时候非用不可.
-
第三坑 故弄玄虚
再细看OP1, 对数组中的某个数加上 10,000,000 .
这个值其实很有讲究, 首先这个数设计的非常随意, 就像是1后面随便按了几个0, 每个位置上的数都加上这么一个同样的数, 也说明了这个值其实并不重要.
其次, 10,000,000 这个数字太大了, 大过了\(n\)和\(m\)的最大值. 加上这么一个数之后, 基本上这个数字就走远了, 再结合 \(r\) 和\(k\) 都被限制在了\([1,n]\)之间, OP2中的查询已经管不到它了. 其实就暗示了OP1根本就是一个无用操作, 是为了把人绕晕而专门设计的.
分析完这三个坑之后, 再来看一下这题是怎么解的. 首先, 来分析一下这些操作到底是在做什么.
以数组 \(a = [4~3~1~2~5]\) 为例子
OP2的输入: \(r=3, k=3\) (这里的假设\(r,k\)都已经被换成了真实的查询) 就是要排除掉 4,3,1
后找第一个大于等于3的数字, 这里可以找到数字5.
OP2的输入: \(r=3, k=2\) 排除掉 4,3,1
后找第一个大于等2的数字, 这里可以找到数字2.
这里的\(r\)限制了数组\(a\)中需检测的范围, 数组\(a\)在\(1\ldots r\)中的值是离散的, 而阈值\(k\) 又卡掉了一部分数字, 这两个限制会让这个问题非常不好处理.
考虑到数组\(a\)其实是\([1,n]\)的一个排列, 我们将其换一种形式来表示.
设数组\(t\)中的\(t[i]\)表示第\(i\)个数出现的位置.
对应数组\(a\)可以转换成数组\(t\)为: \([3~4~2~1~5]\) , 它的含义为第1个数出现在位置3, 第2个数出现在位置4 \(\ldots\)
现在, OP2就可以表示为在数组\(t\)上, 从第\(k\)位开始到结束, 值大于\(r\)的位置上最小下标是什么.
这样我们就可以跳过转换前数组\(a\)中\([1,r]\)之间的数都是离散的状况.
再来看OP1, 数组\(a\)中任何数字加上一个很大值之后都会走的很远, 而\(k\)最大也就为\(n\), 所以\(n+1\)可以是一个默认答案.
解法
可以将数组\(t\)最后附加一个\(n+1\), 并构建一棵线段树.
我们从根节点往下递归:
-
限制1: 数组的下标要大于等于\(k\)(见代码Line-58).
-
限制2: 最终的位置上的值要大于\(r\)(见代码Line-62).
OP2的就是满足这两个条件下的数组\(t\)中最靠左的位置的下标.
但限制2并没有什么规律, 这个递归过程很可能要把下标大于\(k\)的半个子树都遍历一遍, 会导致超时.
因此, 还需要两个优化:
- 能在左子树上找到结果, 就不用再到右子树上找 (见代码 Line-54 和 Line-76)
- 对树上的每个结点维护一个值来记录这个节点下的子树中能存在的最大下标是多少. 如果最大的下标都还少于\(r\), 就不用再往下找下去了, 肯定不满足限制2
再看操作1, 我们只需要把数组中的这个位置上值更新成\(n+1\), 表示该数字已经远去就可以了, 毕竟\(n+1\)总会是一个可能的答案.
代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 100100;
int la = 0;
int n,m;
int a[maxn];
int rt;
int val[maxn<<2];
void tree_init(int l,int r,int root)
{
// 需要优化
//memset(val,0,sizeof(val));
val[root]=0;
if(l==r) return;
int m=(l+r)/2;
tree_init(l,m,root<<1);
tree_init(m+1,r,root<<1|1);
}
void tree_insert(int l, int r,int root, int pos, int v)
{
if(l==r)
{
val[root]=v;
return ;
}
int m = (l+r)/2;
if(pos<=m)
{
tree_insert(l,m,root<<1,pos,v);
}
else
{
tree_insert(m+1,r,root<<1|1,pos,v);
}
//
val[root] = max(val[root<<1],val[root<<1|1]);
}
int query_results;
bool tree_query(int l, int r, int root, int KK, int RR)
{
if(l>query_results)
{
return true;
}
if(r<KK)
{
return false;
}
if(val[root]<=RR)
{
return false;
}
if(l==r)
{
query_results = min(query_results, l);
return true;
}
int m = (l+r)/2;
// l...m
bool is_find = false;
is_find = tree_query(l,m,root<<1,KK,RR);
// m+1, r
if(is_find==false)
{
is_find = tree_query(m+1,r,root<<1|1,KK,RR);
}
return is_find;
}
void debug_tree(int l, int r, int root)
{
printf("%2d: [%d---%d] val: %d\n",root,l,r,val[root]);
if(l==r) return ;
int m = (l+r)/2;
debug_tree(l,m,root<<1);
debug_tree(m+1,r,root<<1|1);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
tree_init(1,n+1,1);
la = 0;
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
// a[i] 出现在 第i号 位置
tree_insert(1,n+1,1,a[i],i+1);
}
/*
tree_insert(1,n+1,1,n+1,n+1);
debug_tree(1,n+1,1);
query_results = n+1;
tree_query(1,n+1,1,4,3);
printf("find: %d\n",query_results);
*/
for(int i=0;i<m;i++)
{
int t, pos, rr, kk;
scanf("%d",&t);
if(t==1)
{
scanf("%d",&pos);
pos = pos^la;
//printf("op1: pos: %d\n",pos);
if(a[pos-1]==-1) continue;
tree_insert(1,n+1,1,a[pos-1],n+1);
a[pos-1]=-1;
//debug_tree(1,n+1,1);
}
else if(t==2)
{
scanf("%d%d",&rr,&kk);
rr = rr^la;
kk = kk^la;
query_results = n+1;
tree_query(1,n+1,1,kk,rr);
//printf("op2: r: %d k: %d find: %d\n",rr,kk,query_results);
la = query_results;
printf("%d\n",query_results);
}
}
}
return 0;
}