单调队列优化DP 上 day46

我超 好jian的t宝

今天也没有看到jls登顶www

一些小理解:dp就是用集合来概括 这样就可以用递推的方式来最优解优化

而dp的状态自然千奇百怪 可谓条条大路

AcWing 135. 最大子序和

想的区间dp 单调队列就是i~j之间的max 

但是无奈区间dp 我不会空间优化

那寄了 没想到可以用前缀和 我们维护一个m的滑动窗口即可

然后要注意的就是 我们最开始要加入下标0 因为这个也是一个合法的下标(我们维护的

是前面长度为m的区间 

然后就是标号要大于m才 pop_front 始终记得我们维护的是前面长度为m的滑动窗口

这样前缀和就直接是不用减一的

这里解释一下为什么不用常用的前缀和-1的写法

因为我们维护的单调队列和求的东西要有直接关系 要是我们这边球的是-1 那么维护的也应该是-1

那么为什么不久直接维护0 0 呢

#include <bits/stdc++.h>
using namespace std;
const int N = 3e5+10;
const int M = 1<<12;
//const int mod = 1e9+7;
#define int long long
#define LL long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int n,m,s[N];
signed main(){
    fast
    cin>>n>>m;
    int ans=-2e9;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        ans=max(ans,s[i]);
        s[i]+=s[i-1];
    }
    deque<int>dq;//维护一个上升队列
    dq.push_back(0);
    for(int i=1;i<=n;i++){
        while(!dq.empty()&&s[dq.back()]>=s[i])dq.pop_back();
        dq.push_back(i);
        while(!dq.empty()&&i-dq.front()>m)dq.pop_front();
        if(!dq.empty()&&dq.front()-i)ans=max(ans,s[i]-s[dq.front()]);
    }
    cout<<ans<<endl;
    return ~~(0^_^0);
}

1087. 修剪草坪

成功把自己绕进去了 哈哈

其实最开始就写对了 但是back 写成 front 以为自己思路出了问题

其实不是啊

我们最开始会想到二维的一个状态表示 f[i][j]表示第i个并且已经连了j个

可是这个确实是找不到啥单调性

那我们可以思考变成一维的情况 f[i]表示第i个并且合法的方案

那状态计算自然就出来了

f[i]=max(f[i-1],f[i-j-1]+s[i]-s[i-j])       (0<=j<=m)

我们把s[i]提出来 max{f[i-j-1]-s[i-j]} 这个的意思就是前面长度为m的滑动窗口中的max值

故可以用单调队列优化 为啥不把s[i]放进去 其实s[i]是一个实时的东西枚举到每一位s[i]就是那个

让后还要考虑的就是i可能和j 相等 是什么情况?

就相当与是一个长度为j+1的线段的和 就是0+s[i]-s[0] 即可 所以我们要设所有f[-1]变成0

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int M = 1<<12;
//const int mod = 1e9+7;
#define int long long
#define LL long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int f[N],s[N];
LL g(int i){
    return f[i-1]-s[i];
}
signed main(){
    fast
    int n,m;cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        s[i]+=s[i-1];
    }
    deque<int>dq;//下降
    dq.push_back(0);
    for(int i=1;i<=n;i++){
        while(!dq.empty()&&(f[i-1]-s[i])>=(f[dq.back()-1]-s[dq.back()]))dq.pop_back();
        dq.push_back(i);
        while(!dq.empty()&&i-dq.front()>m)dq.pop_front();
        f[i]=max(f[i-1],f[dq.front()-1]-s[dq.front()]+s[i]);
    }
    cout<<f[n]<<endl;
    return ~~(0^_^0);
}

1088. 旅行问题

拆成链都知道把

前缀和也要处理是吧

但是这么想 能让我们是线性的做法呢 其实也不是很难想啊

我们只要让每个区间的前缀和都大于0 就可以了

s[j]-s[i]>=0 (i<=j<=i+n)

然后就是模板了

但是这道题让我们正着反着只要一边可以就算可以

但是反向时应该是 o[i]-d[i-1] 这是一个坑点

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6+10;
const int M = 1<<12;
//const int mod = 1e9+7;
#define int long long
#define LL long long
#define endl '\n'
#define Endl '\n'
#define _ 0
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int s[N],o[N],d[N];
bool ans[N];
signed main(){
    fast
    int n;cin>>n;
    for(int i=1;i<=n;i++){
        cin>>o[i];
        o[i+n]=o[i];
        cin>>d[i];
        d[i+n]=d[i];
    }
    for(int i=1;i<=n<<1;i++){
        s[i]+=(o[i]-d[i])+s[i-1];
    }
    deque<int>dq;//min
    for(int i=n<<1;i>=1;i--){
        while(!dq.empty()&&s[i]<=s[dq.back()])dq.pop_back();
        dq.push_back(i);
        while(!dq.empty()&&dq.front()-i>n)dq.pop_front();
        if(!dq.empty()&&s[dq.front()]-s[i-1]>=0)ans[i]=true;
    }
    dq.clear();
    d[0]=d[n];
    for(int i=1;i<=n<<1;i++){
        s[i]=0;
        s[i]+=(o[2*n-i+1]-d[2*n-i])+s[i-1];
    }
    for(int i=n<<1;i>=1;i--){
        while(!dq.empty()&&s[i]<=s[dq.back()])dq.pop_back();
        dq.push_back(i);
        while(!dq.empty()&&dq.front()-i>n)dq.pop_front();
        if(!dq.empty()&&s[dq.front()]-s[i-1]>=0)ans[n-i+1]=true;
    }
    for(int i=1;i<=n;i++){
        if(ans[i])cout<<"TAK"<<endl;
        else cout<<"NIE"<<Endl;
    }
    return ~~(0^_^0);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值