1 条题解

  • 0
    @ 2025-8-24 21:52:50

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar 灵乌路空
    已退役 | 东方众OIer交流群 (幻想乡养老院) : 691090556

    搬运于2025-08-24 21:52:50,当前版本为作者最后更新于2019-07-15 21:30:41,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    先无良宣传一下博客 wwwwwwwwwwww
    文章列表 - 核融合炉心 - 洛谷博客


    知识点 : DPDP 优化 , 单调队列

    • 题意:

      有一个 (N×M)(N\times M) 大小的矩阵
      部分矩阵中的点 带有价值
      可以选择第一行 , 任何一个位置作为起点 .

      对于当前的位置 (i,j) (i,j)\ (表示在第 ii 行第 jj 列)
      可以转移到: (i+1, [jT,j+T] )(i+1,\ [j-T , j+T]\ ) 中任意一个点 (jT>0 , j+tM)(j-T>0\ ,\ j+t\leqslant M)
      并且获得当前所在点的价值 .

      : 可以取得的 最大价值和

      样例解释:
      样例

      1.灵梦从 (1,1)(1,1)出发 : 总价值+3+3 , 现在为 33
      2.灵梦转移到 (2,2)(2,2) : 总价值+3+3 ,现在为 66
      3.灵梦转移到 (3,3)(3,3) : 总价值+3+3 ,现在为 99
      故 , 总价值为 99


    • 分析题意:

      很显然 这是一道 DPDP
      状态转移方程 极其简单 :
      f[i][j]f[i][j] 表示在点 (i,j)(i,j) 能到达的最大价值和 , 则有: $f[i][j] = max(f[i-1][k]) + v[i][j]\ ,\ (k\in [j-T , j+T]\ )$

      • 暴力:
        直接枚举所有点 , 再枚举能转移到他的点 ?
        复杂度 O(N3)O(N^3) 级别 ,
        40Pts40Pts 到手

      考虑优化 :

      • 可知:
        ii 行上点的 f[][]f[][] , 只与第 i1i-1 行有关
        则可将每相邻的两行 , 单独拆分出来考虑 :

      如图:

      滑动窗口

      可以发现 :

      1. 转移到 点(i,j)(i,j) 的点 ,
        i1i-1 行 , 区间 [jT,j+T]\underline{[j-T,j+T]} 中 , f[][]f[][] 最大的点

      2. 转移到 点(i,j+1)(i,j+1) 的点 ,
        i1i-1 行 ,区间 [jT+1,j+T+1]\underline{[j-T+1,j+T+1]} 中 , f[][]f[][] 最大的点

      3. 转移到 点(i,j+2)(i,j+2) 的点 ,
        i1i-1 行 ,区间 [jT+2,j+T+2]\underline{[j-T+2,j+T+2]} 中 , f[][]f[][] 最大的点

      后两个区间 ,
      都可以通过 上一个区间 右移一个单位 得到
      这不禁让我们想到了另一道题 : P1886 滑动窗口
      如果您还未学习过单调队列 , 推荐这篇文章:
      【洛谷日报#9】 [Sweetlemon] 朝花中学OI队的奋斗历程——浅谈单调队列

      这种 滑动窗口型 最值问题 ,
      显然 , 可以通过 单调队列 来进行维护 .

      由上 , 我们便找到了一种合适 DPDP 优化方法: 单调队列优化.


    • 算法实现:

      假设,现在已经更新到第 ii 行 :

      1. 先初始化单调队列 ,
        将能够更新 (i,1)(i,1) 的点(i,k),(k[1,1+T])(i,k) , (k \in [1,1+T]) ,
        加入单调队列
      2. 开始循环 , 更新 [1,M][1,M] 中每一个点 :
        • 将能够转移到 jj 的最右侧一个点 (i1,j+T)(i-1 , j+T) , 加入队列
        • 使用单调队列找到能够转移到 jj 的点的最大f[][]f[][]
        • 用最大的f[][]f[][] 更新 f[i][j]f[i][j]
      3. 清空单调队列 , 外层循环进入下一层 ,更新第 i+1i+1

      最后找到最后一行中 ,
      最大的 f[N][j]f[N][j] ,
      即所求最值 .


    上代码:

    由于使用了手写数组模拟队列 ,
    清空时只需重置头尾指针 , 及队首元素即可 .
    常数吊打 dequedeque

    #include<cstdio>
    #include<ctype.h>
    #include<cstring>
    #define int long long
    #define max(a,b) a>b?a:b
    //=====================================
    const int MARX = 4e3+10;
    int n,m,k,t, now,ans;
    int f[MARX][MARX];
    int head=1,tail=1;//手写双端队列 
    int q[MARX]={9223372036854775807};//为q[0]赋一个极大值,来防止插入元素时越界 
    //=====================================
    inline int read()
    {
    	int fl=1,w=0;char ch=getchar();
    	while(!isdigit(ch) && ch!='-') ch=getchar();
    	if(ch=='-') fl=-1;
    	while(isdigit(ch)){w=w*10+ch-'0',ch=getchar();}
    	return fl*w;
    }
    void in(int x)//向单调队列中插入元素 
    {
    	while(f[now-1][x]>f[now-1][q[tail]] && tail>=head) 
    	  tail--;//取出队尾小于插入元素的数 , 以保证单调性 
    	q[++tail]=x;//插入队尾 
    }
    int find(int x)//查询元素 
    {
    	if(x+t<=m)in(x+t);//将能转移到x点 的 最后一个元素 x+t 插入队列 
    	while(q[head]+t<x) head++;//找到队首第一个能够转移到x的点 
    	return q[head]; 
    }
    //=====================================
    signed main()
    {
    	n=read(),m=read(),k=read(),t=read();
    	while(k--)
    	{
    	  int x=read(),y=read(),w=read();
    	  f[x][y]=w;
    	}
    	
    	for(now=2;now<=n;now++)//从第二行,开始转移 
    	{
    	  for(int i=1;i<=t;i++) in(i);//初始化单调队列 , 满足能够 转移j=1的点 
    	  for(int j=1;j<=m;j++) f[now][j]+=f[now-1][find(j)];// 进行转移 
    	  head=tail=1 , q[1]=0;//清空队列 
    	}
    	
    	for(int i=1;i<=m;i++)//取得最大值 
    	  ans=max(ans,f[n][i]);
    	printf("%lld",ans);
    }
    

    完成了这篇题解 , 东方众信仰 ++++

    • 1

    信息

    ID
    2761
    时间
    2000ms
    内存
    250MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者