hdu4374 One hundred layer

单调队列优化dp

单调队列可以有两个操作:

1、插入一个新的元素,该元素从队尾开始向队首进行搜索,找到合适的位置插入之,如果该位置原本有元素,则替换它。

2、在过程中从队首删除不符合当前要求的元素。

 

单调队列实现起来可简单,可复杂。简单的一个数组,一个head,一个tail指针就搞定。复杂的用双向链表实现。

 

用处:

1、保存最优解,次优解,ect。

2、利用单调队列对dp方程进行优化,可将O(n)复杂度降至O(1)。也就是说,将原本会超时的N维dp降优化至N-1维,以求通过。这也是我想记录的重点

是不是任何DP都可以利用单调队列进行优化呢?答案是否定的。

记住!只有形如 dp[i]=max/min (f[k]) + g[i]  (k<i && g[i]是与k无关的变量)才能用到单调队列进行优化。(降维实质:把状态转移方程分为两部分:只与k有关,只与i有关。 与k有关的状态在前i-1次压入que,i有关的是此次状态转移所需的g[i])

优化的对象就是f[k]。



#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>

using namespace std;

long long mp[105][10005];
long long dp[105][10005];

long long val[10005];
int que[10005];

long long inf=-1e18;

int main()
{
    //cout<<inf<<endl;
    int n,m,x,t;
    while(~scanf("%d%d%d%d",&n,&m,&x,&t))
    {

        for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)
            {
                scanf("%lld",&mp[i][j]);
                dp[i][j]=inf;
            }
        }

        dp[1][x]=mp[1][x];
        long long sum=0;
        for(int i=1; i<=t; i++)
        {
            int cur=x+i;
            if((cur)>m)
                break;

            sum+=mp[1][cur];
            dp[1][cur]=sum+dp[1][x];
        }

        sum=0;
        for(int i=1; i<=t; i++)
        {
            int cur=x-i;
            if((cur)<1)
                break;

            sum+=mp[1][cur];
            dp[1][cur]=sum+dp[1][x];
        }

        for(int i=2; i<=n; i++)
        {
            dp[i][1]=max(dp[i][1],dp[i-1][1]+mp[i][1]);
            long long tsum=0;
            val[1]=dp[i-1][1];
            int head=1,tail=0;
            que[++tail]=1;
            tsum+=mp[i][1];

            for(int j=2; j<=m; j++)
            {
                //cout<<j<<": "<<que[tail]<<endl;
                long long tm=val[que[head]]+tsum;
                tm=max(tm,dp[i-1][j]);
                dp[i][j]=max(dp[i][j],tm+mp[i][j]);
                val[j]=dp[i-1][j]-tsum;
                tsum+=mp[i][j];

                while(head <= tail && val[que[tail]] <= val[j])
                    tail--;
                que[++tail] = j;

                while(head <= tail && j - que[head] >= t)
                    head++;
            }


            dp[i][m]=max(dp[i][m],dp[i-1][m]+mp[i][m]);
            tsum=0;
            val[m]=dp[i-1][m];
            head=1,tail=0;
            que[++tail]=m;
            tsum+=mp[i][m];

            for(int j=m-1; j>=1; j--)
            {
                long long tm=val[que[head]]+tsum;
                tm=max(tm,dp[i-1][j]);
                dp[i][j]=max(dp[i][j],tm+mp[i][j]);
                val[j]=dp[i-1][j]-tsum;
                tsum+=mp[i][j];

                while(head <= tail && val[que[tail]] <= val[j])
                    tail--;
                que[++tail] = j;

                while(head <= tail &&  que[head]-j >= t)
                    head++;
            }
        }

        long long ans=inf;
        for(int i=1; i<=m; i++)
            ans=max(ans,dp[n][i]);

        printf("%lld\n",ans);

      /*  for(int i=1; i<=n; i++)
        {
            for(int j=1; j<=m; j++)


                cout<<dp[i][j]<<" ";
            cout<<endl;
        }
        */

    }
}

/*
 for(int j=1;j<=m;j++)
                cout<<dp[i][j]<<" ";
            cout<<endl;


            3 4 2 2
-12 -3 23 -5
-2 -2 -2 -4
2 1 -3 7

3 5 3 5
1 1 1 0 0
1 1 1 1 1
0 1 1 1 1
*/


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值