洛谷 P3800 Power收集

博客介绍了洛谷P3800题目的解题思路,这是一个关于矩阵权值最大和的问题。博主首先提出了暴力解决方法,即使用动态规划dp[i][j]表示到达i,j的最大权值和,但这种方法会导致TLE。接着,博主通过类比滑动窗口优化,提出利用优先队列在O(nm)时间内优化解决方案,从而避免了与时间限制相关的错误。" 124069719,13385908,Mac与Win10共享文件夹:Parallels Desktop设置指南,"['macos', 'windows', '虚拟机', '文件共享', 'Parallels Tools']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意简述

首先这是个东方题。。。
灵梦在一个矩阵上,每次珂以往下走一格(竖向),并且珂以横向瞬移(不需要时间,只能一次,也不会途径任何点,直接到终点)<=T步,求走到最后一行路径上的点权值和最大。
(给定矩阵的方式类似稀疏矩阵,k行,每行3个数x,y,z表示在点(x,y)上权值为z。其余权值为0.矩阵大小:两条边长均<=4000)

数据

输入
3 3 4 1
1 1 3
1 2 1
2 2 3
3 3 3
输出
9
//这个是我为了娱乐而写的,不是实际输出

解释


(我自己画的,好心吧。。。)

思路

一个暴力的思路: d p [ i ] [ j ] dp[i][j] dp[i][j]表示走到 i , j i,j i,j的最大权值和,则 d p [ i ] [ j ] = m a x dp[i][j]=max dp[i][j]=max{ d p [ i − 1 ] [ k ] ( − t &lt; = k &lt; = t ) dp[i-1][k](-t&lt;=k&lt;=t) dp[i1][k](t<=k<=t)} + m p [ i ] [ j ] +mp[i][j] +mp[i][j],其中 m p [ i ] [ j ] mp[i][j] mp[i][j]是记录矩阵的(Q:为什么不用 m a p map map?A:会重名, m a p map map是一种开挂的类型,前面见过)。
珂是这样是 O ( n m t ) O(nmt) O(nmt)的,直接就 TLE了好么。。。
有一个题目叫滑动窗口,就是一个定长度的窗口从左到右滑,每次求最大值。
仔细观察一下我们的 d p dp dp数组,会发现和那个题目有点像。
不像?举个栗子:

假如每行有 10 10 10个数, t = 1 t=1 t=1,现在 D P DP DP到了第 i i i行。设 M ( l , r ) M(l,r) M(l,r) d p [ i − 1 ] [ l ] dp[i-1][l] dp[i1][l] d p [ i − 1 ] [ r ] dp[i-1][r] dp[i1][r]中的最大值。
那么, d p [ i ] dp[i] dp[i]应该从 1 1 1 10 10 10依次是:
S ( 1 , 2 ) , S ( 1 , 3 ) , S ( 2 , 4 ) , S ( 3 , 5 ) , S ( 4 , 6 ) , S ( 5 , 7 ) , S ( 6 , 8 ) , S ( 7 , 9 ) , S ( 8 , 10 ) , S ( 9 , 10 ) S(1,2),S(1,3),S(2,4),S(3,5),S(4,6),S(5,7),S(6,8),S(7,9),S(8,10),S(9,10) S(1,2),S(1,3),S(2,4),S(3,5),S(4,6),S(5,7),S(6,8),S(7,9),S(8,10),S(9,10)
诶?这个除了两边边界不太一样,别的真的都好像哦!
更准确的说,这应该跟接近于洛谷P1440,与滑动窗口不同的是,这个题目如果长度不足就全部考虑,就和我们的转移方程是一样的了。

那么现在我们就珂以维护一个优先队列, O ( m ) O(m) O(m)转移出第 i i i行所有的 d p dp dp值。那这样我们的总时间复杂度就是 O ( n m ) O(nm) O(nm)的了,把 t t t整个去掉了,准确来讲是优化掉了。

代码:

#include<bits/stdc++.h>
#define N 4010
using namespace std;

int mp[N][N];
int n,m,k,t;
void Input()
{
    scanf("%d%d%d%d",&n,&m,&k,&t);
    for(int i=1;i<=k;i++)
    {
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        mp[x][y]=w;//暴力存
    }
}

int dp[N][N];
int Q[N][2];//单调队列
void Solve()
{

    for(int i=1;i<=n;i++)
    {
        int head=1,tail=0;
        //这里如果习惯不一样,千万不要乱抄。如果要抄,那么一定要注意head和tail的边界问题!!!
        //!!!
        //!!!
        for(int j=1;j<=min(m,t);j++)//把长度为t的先弄好(min(m,t)是防爆用的)
        {
            while(Q[tail][0]<=dp[i-1][j] and head<=tail)//不断去掉队尾小的
            {
                tail--;
            }
            ++tail;
            Q[tail][0]=dp[i-1][j];
            Q[tail][1]=j;//然后加入队列
        }
        for(int j=1;j<=m;j++)
        {
            if (j+t<=m)//开始滑动!
            {
                while(Q[tail][0]<=dp[i-1][j+t] and head<=tail)
                {
                    tail--;
                }
                ++tail;
                Q[tail][0]=dp[i-1][j+t];
                Q[tail][1]=j+t;
                //加入队列
            }
            if (Q[head][1]<j-t)//及时踢出队列
            {
                ++head;
            }
            dp[i][j]=Q[head][0]+mp[i][j];//设置答案
        }
    }

    int ans=-1;
    for(int i=1;i<=m;i++)
    {
        ans=max(ans,dp[n][i]);
    }
    printf("%d\n",ans);
    //然后这样就是O(nm)了。。。
}
main()
{
    Input();
    Solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值