[luogu6466]分散层叠算法
做法1
对于每一个询问,直接暴力在每一个序列中二分查询
时间复杂度为$o(nk)-o(k\log n)$
做法2
将所有序列合并后排序,并对每一个元素预处理出每个序列中第一个大于等于其的元素(位置),那么只需要在总序列中二分并输出该位置预处理的答案即可
关于这个预处理,显然只需要从后往前扫描一遍总序列即可
时间复杂度为$o(nk^{2})-o(k+\log nk)$
做法3
使用分治归并,并对合并后序列的每一个元素预处理出参与合并的两个序列第一个大于等于其的元素位置
对于查询,只需要在总序列中二分一次,并根据预处理的信息递归下去即可
时间复杂度为$o(nk\log k)-o(k+\log nk)$
做法4
事实上,只需要将下标为偶数的项参与合并,此时即找到第一个大于等于"其"的偶数项,再判断上一项是否大于等于"其"即可,这样查询的复杂度并没有变化
(另外,为了避免特判可以强制将最后一项也加入)
沿用做法3,并做此优化,归纳可得每一个位置上的序列长度都为$n$,复杂度即降为$o(nk)$
当然,直接沿用做法2也是可以的,即令$\{b_{k}\}=\{a_{k}\}$且$\{b_{i}\}$为$\{a_{i}\}$和$\{b_{i+1}\}$合并的结果,同样在合并后预处理相同的信息,通过此优化不难证明$\{b_{i}\}$的长度和为$o(nk)$
时间复杂度为$o(nk)-o(k+\log nk)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 10005 4 #define K 105 5 #define D 2 6 int n,k,m,d,x,ans,num[11],a[K][N],Pos[2],b[K][N<<1],pos[K][N<<1][2]; 7 int read(){ 8 int x=0; 9 char c=getchar(); 10 while ((c<'0')||(c>'9'))c=getchar(); 11 while ((c>='0')&&(c<='9')){ 12 x=x*10+(c-'0'); 13 c=getchar(); 14 } 15 return x; 16 } 17 void write(int x,char c='\0'){ 18 while (x){ 19 num[++num[0]]=x%10; 20 x/=10; 21 } 22 if (!num[0])putchar('0'); 23 while (num[0])putchar(num[num[0]--]+'0'); 24 putchar(c); 25 } 26 int main(){ 27 n=read(),k=read(),m=read(),d=read(); 28 for(int i=1;i<=k;i++) 29 for(int j=1;j<=n;j++)a[i][j]=read(); 30 for(int i=k;i;i--){ 31 int x=1,y=D; 32 while ((x<=n)||(y<=b[i+1][0])){ 33 if ((x<=n)&&((y>b[i+1][0])||(a[i][x]<b[i+1][y]))){ 34 b[i][++b[i][0]]=a[i][x]; 35 pos[i][b[i][0]][0]=x++; 36 } 37 else{ 38 b[i][++b[i][0]]=b[i+1][y]; 39 pos[i][b[i][0]][1]=y; 40 if (y==b[i+1][0])y+=D; 41 else y=min(y+D,b[i+1][0]); 42 } 43 } 44 memset(Pos,0,sizeof(Pos)); 45 for(int j=b[i][0];j;j--) 46 for(int p=0;p<2;p++){ 47 if (pos[i][j][p])Pos[p]=pos[i][j][p]; 48 pos[i][j][p]=Pos[p]; 49 } 50 } 51 for(int i=1;i<=m;i++){ 52 x=read(),x^=ans; 53 ans=0; 54 int y=lower_bound(b[1]+1,b[1]+b[1][0]+1,x)-b[1]; 55 for(int j=1;j<=k;j++){ 56 if (pos[j][y][0])ans^=a[j][pos[j][y][0]]; 57 if (!pos[j][y][1])break; 58 y=pos[j][y][1]; 59 while ((y>1)&&(b[j+1][y-1]>=x))y--; 60 } 61 if (i%d==0)write(ans,'\n'); 62 } 63 return 0; 64 }
拓展
给定一张$k$个点的DAG,每个点上有一个长为$n$的单调不下降序列$\{a_{i}\}$
$m$次询问$x$和一条路径,求出每一个路径上的序列中第一个大于等于$z$的元素
保证每一个点的入度和出度均不超过$d$
题解:
类似于做法4,在叶子上令$\{b_{leaf}\}=\{a_{leaft}\}$且$\{b_{i}\}$为$\{a_{i}\}$和$\forall (i,j)\in E,\{b_{j}\}$合并的结果(后者合并时只取下标为$d+1$的倍数的项),并预处理相同的信息
令$L_{i}$为$i$上序列的长度,则有$L_{i}=n+\frac{\sum_{(i,j)\in E}L_{j}}{d+1}$,进而有
$$
\sum_{i\in V}L_{i}=\sum_{i\in V}(n+\frac{\sum_{(i,j)\in E}L_{j}}{d+1})\le \sum_{i\in V}(n+\frac{d}{d+1}L_{i})
$$
将其化简,也即$\sum_{i\in V}L_{i}\le (d+1)nk$
简单分析,可以发现预处理的时空复杂度均为$o(d\sum_{i\in V}L_{i})$,由此也即$o(d^{2}nk)$
另外,由于只取了$d+1$的倍数项,查询时最多要向前找$d$次,即最坏要找$o(dk)$次
(显然$d$要很小此做法才较优,因此二分做到$o(k\log d)$没有意义)
时间复杂度为$o(d^{2}nk)-o(dk+\log nk)$