数据结构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表+单调队列)

模板 - 数据结构 - 单调队列/单调栈

HDU 5289 Assignment(单调队列)

bzoj 1047: [HAOI2007]理想的正方形单调队列

机房测试9:gift(单调队列优化dp)

POJ 3709 K-Anonymous Sequence (单调队列优化)