题目描述
Problem Description
BNU ACM校队有n名队员,从1到n标号,每名队员根据自身情况拥有一个特征值,其中第i名队员的特征值是a[i]。现在BOSS问了m个问题,每个问题给定[l,r],要求小Q同学马上从标号位于区间[l,r]内的队员中选出两名队员,使得这两名队员的特征值相同,并且不默契度要尽可能小,两名队员的不默契度定义为两名队员标号之差的绝对值。对此小Q同学倍感压力,急需你的帮助。
Input
第一行包含2个整数n、m。
第二行包含n个整数a[1]、a[2]、…、a[n],保证0<=a[i]<2^31。
接下来m行,每行包含2个整数l、r,请注意所给的l、r均是经过加密的,解密方式是l=l xor lastans、r=r xor lastans,其中lastans表示上一次操作的输出结果,初始lastans=0,保证解密后1<=l<=r<=n。
对于100%的数据,1<=n,m<=500000。Output
输出m行,每行包含一个整数,表示最小的两名队员的不默契度,如果不能选出满足条件的两名队员,请输出-1。
Sample
input output 5 3
1 1 2 5 2
1 5
3 5
-4 -61
-1
2
分析
- 题目大意就是给一串数a[],以及一堆询问
[l,r]
,求此闭区间内距离最小的两个相等的数的距离。 - 题目中的“解密”其实就意味着强制在线,所以就用在线的方法做。
- 可以先处理好每个元素前后离它最近的那个和它相等的元素,(即 next[] 和 last[])这样比较的时候只需看看 i 和 next[i] 的编号差就行了。
- 然而,对于每个询问直接暴力(即
for (i=l~r) upd(ans,next[i]-i)
的话肯定超时,每次询问都 O(r-l),所以可以想到分块来预处理出些答案。 dp[i][j]
代表第 i 到第 j 这些分块的区间中的答案。- 注意,我的程序中下标都是从 0 开始,为了方便求某个点所在的分块(假设 K 个点分一块,那么第 i 个点所在的分块就是 i/K)。
- 首先处理出每个点的 next[] 值,同时利用它们来预先处理好一部分 dp[][](假设当前求出了 next[i] ,那么
dp[i][i/K]
就将它更新为它与next[i]-i
的较小值)。 - 显然到目前为止, dp[][] 还不是完整的,只有每个 i 与 next[i] 所在的分块构成的区间才是正确的值,那么,我们再用
dp[i+1][j]
和dp[i][j-1]
来更新一下dp[i][j]
,这样就得出完整的dp[][]了。 - 到此,预先处理的东西就完成了,剩下的就是对于每个询问求出答案。先对区间
[l,r]
中不包含 l,r 的区间的分块所得的 dp[][] 赋值给 ans,再对于剩下左边和右边的点暴力枚举(其实这时就不会太久了),左边用next[i]-i
来更新答案,右边用i-last[i]
来更新答案,就完成了。
- 首先处理出每个点的 next[] 值,同时利用它们来预先处理好一部分 dp[][](假设当前求出了 next[i] ,那么
程序
#include <cstdio>
#include <algorithm>
#include <cstring>
#define _ (scanf("%d",&o),o)
using namespace std;
int dp[6300][6300],next[500010],last[500010],a[500010],b[500010],n,m,ans,l,r,L,R,K,o;
void upd(int x,int y,int z){dp[x/K][y/K]=min(dp[x/K][y/K],z);}
bool cmp(int x,int y){return a[x]==a[y] ? x<y:a[x]<a[y];}
int main(){
freopen("1.txt","r",stdin);
scanf("%d%d",&n,&m);
for (K=1; K*K*K<n; K++); //得出K,大概为n^(1/3)
for (int i=0; i<n; i++) scanf("%d",&a[i]);
for (int i=0; i<n; i++) b[i]=i; sort(b,b+n,cmp); //适当地离散化一下,相当于双关键字排序
memset(dp,0x7f,sizeof(dp));
for (int i=0; i<n; i++) last[i]=-1;
for (int i=0; i<n-1; i++) if (a[b[i]]==a[b[i+1]]) next[b[i]]=b[i+1],last[b[i+1]]=b[i],upd(b[i],b[i+1],b[i+1]-b[i]); //求next[],顺便预处理一下dp[][]
for (int i=0,H=n/K; i<H; i++) for (int j=i+1; j<=H; j++){ //求dp[][]
upd(i*K,j*K,dp[i][j-1]);
upd(i*K,j*K,dp[i+1][j]);
}
while (m--){
l=(_^ans)-1,r=(_^ans)-1;
L=l/K, R=r/K; //l与e所在的分块
ans=dp[L+1][R-1]; //l与r中间的分块(不包含i,r)
for (int i=l,H=min((L+1)*K-1,r); i<=H; i++) if (next[i]!=0 && next[i]<=r) ans=min(ans,next[i]-i); //l所在的那一个分块
for (int i=max(R*K,l); i<=r; i++) if (last[i]!=0 && last[i]>=l) ans=min(ans,i-last[i]); //r所在的那个分块
printf("%d\n",ans<=500000 ? ans:ans=-1);
}
}
提示
- 我们在求 next[] 的时候,用
n2
暴力做法求的话起来就很不好,所以可以利用一下 C++ 自带的
sort()
函数来进行一个双关键字排序,相当于把数列 a[] 重新排一下序使得数值相等的元素靠在一起,而且按从小到大排列,而且数值相等的每一组里面的元素相对位置(即先后顺序)不变。程序中我用了数组 b[] 来记录排过序后的数列中每个数的位置( b[i] 表示 a[i] 这个元素变到第 b[i] 这里来了)。举个例子 -
# 排序前 排序后 数列
编号1 3 5 1 2 1 8 5 5
1 2 3 4 5 6 7 8 91 1 1 2 3 5 5 5 8
1 4 6 5 2 3 8 9 7 为了程序的简洁,我写了个函数来代替长长的更新 dp[][] 操作(这样就不用写很多次了),可是我传进去的是两个点的位置以及要比较的值,结果在下面我没举的是分块的编号,我直接把分块编号穿进去了,虽然样例过了,可是交上去评测时就 Wa 了。
不过竟然能有55分!- 还有一个问题是在提交并 Ac 的第二天才想起来的,我一开始只是记录了 next[] ,并没有用 last[] ,右边部分也是用 next[] 来更新答案,早上想到了这样似乎有 bug,自己出了个数据才发现错了。
不过这样竟然能Ac! - 你选不同的 K (每块的元素个数),跑出的时间是不同的,提交时,我测了几个 K 的值,发现 K 取 n13 时比 n12 块一些。
- 其实还能用 RMQ 算法做这道题,大家可以试一试。