GalaxyOJ-64 (分块)

来源:互联网 发布:2017全球社交网络排名 编辑:程序博客网 时间:2024/06/06 11:39

题目描述

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 -6 1
-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] 来更新答案,就完成了。

程序

#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 9 1 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 算法做这道题,大家可以试一试。
0 0
原创粉丝点击