题目:http://www.gdfzoj.com/oj/problem/64
描述:给一个长度为n的数组a[i],和m个区间<l,r>,求<l,r>内最小子区间<i,j>使a[i] == a[j](n,m<5*10^5)
一开始想到 离散化+RMQ,但后来发现RMQ不能像维护区间最值一样用max/min维护该问题
正解是主席树或分块,时限5s,但分块写好的话1s也能过(746ms水过),其他题解写在注释里了
关于块的大小的推理可能不是很严谨,具体还要看不同题目的数据范围.
下面放代码
#include <cstdio> #include <map> #define k(x) ((x-1) / K+1) //x属于分块中的第k(x)块 using namespace std; const int N = 500010,K = 150,M = N / K + 10; //K为块的大小 当K=1000时耗时1620ms,K=150时耗时746ms(相信玄学) //M为块数 int a[N],f[M][M],nxt[N],pre[N],n,m,i,j,l,r,ans; //f为分块数组 f[i][j]表示第i块到第j块的最小方案 //想不到只用next数组的方法,于是nxt[i]表示 a[i]=a[nxt[i]] 且 nxt[i] > i 的最小值 //pre[i]表示 a[pre[i]]=a[i] 且 pre[i] < i 的最大值 map <int,int> q; //偷懒用 map 离散化 inline int min(int a,int b) { if (!a) return b; if (!b) return a; return (a < b) ? a : b; } //求a,b的最小值,特判a==0 b==0 int main() { scanf("%d%d",&n,&m); for (i=1;i<=n;i++) { scanf("%d",a+i); j = q[a[i]]; if (j) { nxt[j] = i; pre[i] = j; f[k(j)][k(i)] = min(f[k(j)][k(i)],i-j); } q[a[i]] = i; } //一个简单的用 map 实现的离散化 复杂度约 O(n log n) for (i=1;i<n/K+1;i++) for (j=1;i+j<n/K+1;j++) { f[i][i+j] = min(f[i][i+j-1],f[i][i+j]); f[i][i+j] = min(f[i][i+j],f[i+1][i+j]); } //i为区间长度,j为起始位置,一个预处理 //f[i][i+j] = min(f[i][i+j],f[i+1][i+j],f[i][i+j+i]) //复杂度 O(n²/k²) while (m--) { scanf("%d%d",&l,&r); l ^= ans; r ^= ans; ans = f[k(l)+1][k(r)-1]; //块内最小答案 O(1) for (i=l;i<k(l) * K && i<=r;i++) if (nxt[i] <= r && nxt[i]) ans = min(ans,nxt[i] - i); l = i - 1; for (i=r;i>(k(r)-1) * K && i>=l;i--) if (pre[i] >= l) ans = min(ans,i - pre[i]); //暴力枚举块外答案(pre和nxt的作用) 复杂度 O(2k) if (!ans) ans = -1; printf("%d\n",ans); } //综上,时间复杂度约 O(n log n + n²/k² + 2mk) //代入n=m=500000,k≈540。但n,m<500000,因此k取约150-400是有一定道理的。 }