数据结构k维滑窗:扩展到k维的单调队列
Posted kkkek
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构k维滑窗:扩展到k维的单调队列相关的知识,希望对你有一定的参考价值。
k维滑窗:扩展到k维的单调队列
假如要求一个高维度的空间(数组,矩阵,长方体,这些固定窗口型区域)的固定区域极值时,且满足高纬度空间是在复杂度范围内时,可以用这个做法, 时间复杂度为 (mathcal{O(高维空间容量 imes 维度)})。这个维度只是一个小常数,可以忽略,所以也可以说,时间复杂度为 (mathcal{O(高维空间容量)})。
做法, 以求极大值为例:
首先,是一维, 假设长度为 (n) 的数组,对于所有(i) ,要求所有 区间([i,min(i+d,n)]) 里的区间最大值,那就是一维滑窗:
(code:)
#pragma GCC optimize("-O2")
#include<bits/stdc++.h>
#define reg register
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=2e3+5;
const double ep=1e-8;
struct Q{
int v,id;
}q[maxn];//队列
int a[maxn];//原数组
int b[maxn];//结果
int main()
{
int n,d;
scanf("%d%d",&n,&d);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int l=1,r=0;
for(int i=1;i<=d+1;i++)
{
while(r>=l&&q[r].v<=a[i])r--;
q[++r]={a[i],i};
}
b[1]=q[l].v;
for(int i=2;i<=n;i++)
{
while(q[l].id<i&&l<=r)l++;
if(i+d<=n)
{
while(r>=l&&q[r].v<=a[i+d])r--;
q[++r]={a[i+d],i+d};
}
b[i]=q[l].v;
}
for(int i=1;i<=n;i++)printf("%3d",a[i]);putchar(10);
for(int i=1;i<=n;i++)printf("%3d",b[i]);
}
其次是二维, (n imes m) 矩阵,要求 ( d_n imes d_m) 内窗口的极大值:
- 首先把二维数组分成 (n) 个长度为 (m) 的一维数组, 先处理好每个一维数组,处理好每个以 (i) 为起点 (i+d_m) 为终点的区间极值, 更新到新数组中。
- 其次,再以新的数组, 将其分成 (m) 个长度为 (n) 的一维数组, 再次处理每个一维数组,处理好每个以 (i) 为起点 (i+d_n) 为终点的区间极值, 更新到另一个新数组中, 得到的新数组就是答案。
三维同理,将三维分割成第三维度个 ( n imes m) 的二维数组,再处理。
例题:
牛客 “科大讯飞杯”第18届上海大学程序设计联赛春季赛暨高校网络友谊赛: K 迷宫
题意:
有一个 (n imes m) 的矩阵迷宫,从上到下依次给行编号为 (0,1,...,n-1), 从左到右依次给列编号为 (0,1,2...,m-1)。 游戏规则是: 从起点出发,每步操作可以移动到上、下、左、右四个方向的空地上,直到终点。
游戏中,从起点到终点并不一定会有直接的通路, 玩家最多可以使用一次穿梭技能,: 从一块空地移动到距离至多为 (d) 另一片空地上(起点和终点也视为空地),无论中间是否有障碍。该技能使用时计为一步操作。
询问最少需要多少步才能到达终点,并给出一个方案(移动步骤)。
输入描述:
第一行 (3) 个整数, (n,m,d), ((1le n,mle2000,,0le dle2000)), 分别表示行数,列数,和穿梭的最大距离。
接下来 (n) 行,每行一个长度为 (m) 的字符串,表示地图,其中 (S) 为起点, (T) 为终点, (‘.‘) 为空地, ( m ‘X‘) 为障碍, 输入保证有且仅有一个起点和一个终点。
输出描述:
第一行输出一个整数 (t) 表示最少所需要的步骤。
接下来 (t+1) 行, 每行输出两个整数 (x_i,y_i),中间以空格分隔, 表示每一步所经过的坐标。其中,第一行和最后一行应分别对应起点和终点。
特别地,如果没有可以走到终点的方案, 则再一行输出 (-1)。
答案不唯一, 符合要求即可。
大致做法:
先对起点和终点分别做一次 (bfs), 然后对终点(起点)处理结果, 做长度为 (d imes d) 的二维滑窗处理, 然后遍历起点(终点)处理结果, 以当前点为中心的四方四个窗口取最小值, 最小值+起点(终点)的数据就是当前点的最优步数, 然后对所有结果取最小值。时间复杂度是 (mathcal{O(n imes m)})。
当时没想到二维滑窗,只会一维滑窗, 所以傻傻用了二维线段树去做,??(mathcal{O(n imes m imeslog n imeslog m)}), 太蠢了,只能过 (70\%) 的数据。
(code:)
#pragma GCC optimize("-O2")
#include<bits/stdc++.h>
#define min(a,b) (a)<(b)?(a):(b)
#define max(a,b) (a)>(b)?(a):(b)
#define reg register
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=2e3+5;
const double ep=1e-8;
void read(int&x)
{
char c;
while(!isdigit(c=getchar()));x=c-‘0‘;
while(isdigit(c=getchar()))x=(x<<1)+(x<<3)+c-‘0‘;
}
void write(int x)
{
if(x<0)putchar(‘-‘),x=-x;
if(x>=10)write(x/10);
putchar(x%10+‘0‘);
}
char s[maxn][maxn];
int sx,sy,ex,ey,n,m,dis;
int en[maxn][maxn],sn[maxn][maxn];
int d[4][2]={-1,0,0,1,1,0,0,-1};
struct P{
int x,y;
};
queue<P>que;
P las[maxn][maxn],lae[maxn][maxn];
void bfs1()
{
memset(en,inf,sizeof(en));
en[ex][ey]=0;que.push({ex,ey});
reg P u;reg int tx,ty;
while(!que.empty())
{
u=que.front();que.pop();
for(auto f:d)
{
tx=u.x+f[0];ty=u.y+f[1];
if(s[tx][ty]==‘X‘||en[tx][ty]<=en[u.x][u.y]+1||tx<1||ty<1||tx>n||ty>m)continue;
en[tx][ty]=en[u.x][u.y]+1;lae[tx][ty]={u.x,u.y};
if(tx==sx&&ty==sy)return;
que.push({tx,ty});
}
}
}
void bfs2()
{
while(!que.empty())que.pop();
memset(sn,inf,sizeof(sn));
sn[sx][sy]=0;que.push({sx,sy});
reg P u;reg int tx,ty;
while(!que.empty())
{
u=que.front();que.pop();
for(auto f:d)
{
tx=u.x+f[0];ty=u.y+f[1];
if(s[tx][ty]==‘X‘||sn[tx][ty]<=sn[u.x][u.y]+1||tx==sx&&ty==sy||tx<1||ty<1||tx>n||ty>m)continue;
sn[tx][ty]=sn[u.x][u.y]+1;las[tx][ty]={u.x,u.y};
if(tx==ex&&ty==ey)return;
que.push({tx,ty});
}
}
}
P sta[maxn*maxn];int cnt;
struct Q {
int v,x,y;
bool operator<(const Q&q)const{return v<q.v;}
}q[maxn],qq,a[maxn][maxn],b[maxn][maxn];
inline Q getmin(int x,int y)
{
int x1=max(1,x-dis),y1=max(1,y-dis);
return min(min(b[x1][y1],b[x1][y]),min(b[x][y1],b[x][y]));
}
int main()
{
read(n);read(m);read(dis);
for(reg int i=1;i<=n;i++)
{
gets(s[i]+1);
for(int j=1;j<=m;j++)
if(s[i][j]==‘S‘)sx=i,sy=j;
else if(s[i][j]==‘T‘)ex=i,ey=j;
}
bfs1();bfs2();
for(int i=1,l,r,up=min(dis+1,m);i<=n;i++)
{
l=1,r=0;
for(int j=1;j<=up;j++)
{
while(r&&q[r].v>=en[i][j])r--;
q[++r]={en[i][j],i,j};
}
a[i][1]=q[l];
for(int j=2;j<=m;j++)
{
while(q[l].y<j&&l<=r)l++;
if(j+dis<=m)
{
while(r>=l&&q[r].v>=en[i][j+dis])r--;
q[++r]={en[i][j+dis],i,j+dis};
}
a[i][j]=q[l];
}
}
for(int j=1,l,r,up=min(dis+1,n);j<=m;j++)
{
l=1,r=0;
for(int i=1;i<=up;i++)
{
while(r&&q[r].v>=a[i][j].v)r--;
q[++r]={a[i][j].v,i,a[i][j].y};
}
b[1][j]=q[l];
for(int i=2;i<=n;i++)
{
while(q[l].x<i&&l<=r)l++;
if(i+dis<=n)
{
while(r>=l&&q[r].v>=a[i+dis][j].v)r--;
q[++r]={a[i+dis][j].v,i+dis,a[i+dis][j].y};
}
b[i][j]=q[l];
}
}
P ss,ee;int ans=inf;
if(en[sx][sy]!=inf)
{
ans=en[sx][sy];ss={ex,ey};ee={sx,sy};
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
qq=getmin(i,j);
if(sn[i][j]<inf&&ans>sn[i][j]+qq.v+1)
{
ans=sn[i][j]+qq.v+1;
ss={i,j};
ee={qq.x,qq.y};
}
}
}
write(ans>=inf?-1:ans);putchar(10);
if(ans>=inf)return 0;
ans++;
if(ee.x==sx&&ee.y==sy)
{
while(ans)
{
write(ee.x-1);putchar(32);write(ee.y-1);putchar(10);
ee=lae[ee.x][ee.y];ans--;
}
return 0;
}
while(1)
{
sta[++cnt]=ss;
if(ss.x==sx&&ss.y==sy)break;
ss=las[ss.x][ss.y];
}
while(cnt)
{
write(sta[cnt].x-1);putchar(32);write(sta[cnt].y-1);putchar(10);
ans--;cnt--;
}
while(ans)
{
write(ee.x-1);putchar(32);write(ee.y-1);putchar(10);
ee=lae[ee.x][ee.y];ans--;
}
}
以上是关于数据结构k维滑窗:扩展到k维的单调队列的主要内容,如果未能解决你的问题,请参考以下文章
牛客多校2021 K.King of Range(ST表+单调队列)