dp 单调性优化总结

Posted chdy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dp 单调性优化总结相关的知识,希望对你有一定的参考价值。

  对于单调性优化其实更多的是观察dp的状态转移式子的单调性 进而用优先队列 单调队列 二分查找什么的找到最优决策 使时间更优。

技术图片

对于这道题就是单调性优化的很好的例子

首先打一个暴力再说。

f[i][j]表示前i个木匠刷前j个木板所得到的最大价值

f[i][j]=max(f[i][j],f[i-1][j]);

f[i][j]=max(f[i][j],f[i][j-1])

f[i][j]=max(f[i][j],f[i-1][k]+(j-k)*r(j));(k<t(j)<=j)&&(j-k<=l(j));

这样的话枚举 i j k 时间复杂度 n^2m;

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
#define t(x) t[x].target
#define r(x) t[x].receive
#define l(x) t[x].l
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
const int MAXN=16002,maxn=102;
struct wy
{
    int l,receive,target;
    friend int operator <(const wy x,wy y)
    {
        return x.target<y.target;
    }
}t[MAXN];
int n,m;
int f[maxn][MAXN];//f[i][j]表示前i个木匠刷前j个木板所得到的最大价值
//f[i][j]=max(f[i][j],f[i-1][j]);
//f[i][j]=max(f[i][j],f[i][j-1]);
//f[i][j]=max(f[i][j],f[i-1][k]+(j-k)*r(j));(k<t(j)<=j)&&(j-k<=l(j));
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        l(i)=read();
        r(i)=read();
        t(i)=read();
    }
    sort(t+1,t+1+m);
    //for(int i=1;i<=m;i++)cout<<l(i)<<‘ ‘<<r(i)<<‘ ‘<<t(i)<<endl;
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            f[i][j]=max(f[i][j],f[i-1][j]);
            f[i][j]=max(f[i][j],f[i][j-1]);
            if(j>=t(i)&&j-l(i)<t(i))
                for(int k=max(0,j-l(i));k<t(i);k++)
                        f[i][j]=max(f[i][j],f[i-1][k]+(j-k)*r(i));
        }
    }
    put(f[m][n]);
    return 0;
}
View Code

而 n<=16000 m 100 这就直接T飞了考虑将其优化。

f[i][j]=max{f[i-1][k]-k*r(i)}+j*r(i);

so easy 单调队列优化一下结束 保存决策即可

至于一些细节需要注意 不过单调队列这样优化也算是很常见的吧。

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
#define t(x) t[x].target
#define r(x) t[x].receive
#define l(x) t[x].l
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
//f[i][j]表示前i个木匠刷前j个木板所得到的最大价值
//f[i][j]=max(f[i][j],f[i-1][j]);
//f[i][j]=max(f[i][j],f[i][j-1]);
//f[i][j]=max(f[i][j],f[i-1][k]+(j-k)*r(i));(k<t(j)<=j)&&(j-k<=l(j));
//考虑将其优化 f[i][j]=max{f[i-1][k]+j*r(i)-k*r(i)};
//f[i][j]=max{f[i-1][k]-k*r(i)}+j*r(i);
//so easy 单调队列优化一下结束 保存决策即可
const int MAXN=16002,maxn=102;
struct wy
{
    int l,receive,target;
    friend int operator <(const wy x,wy y)
    {
        return x.target<y.target;
    }
}t[MAXN];
int n,m;
int f[maxn][MAXN];
int q[MAXN],l,r;
int kk(int v,int x){return f[v-1][x]-x*r(v);}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        l(i)=read();
        r(i)=read();
        t(i)=read();
    }
    sort(t+1,t+1+m);
    //for(int i=1;i<=m;i++)cout<<l(i)<<‘ ‘<<r(i)<<‘ ‘<<t(i)<<endl;
    for(int i=1;i<=m;i++)
    {
        l=r=0;l=1;if(t(i)-l(i)<=0)q[++r]=0;
        for(int j=1;j<=n;j++)
        {
            f[i][j]=max(f[i-1][j],f[i][j-1]);
            if(j>=t(i))
            {    
                while(l<=r&&q[l]<(j-l(i)))l++;
                if(l<=r)f[i][j]=max(f[i][j],kk(i,q[l])+j*r(i));
            }
            if(j>=t(i)-l(i)&&j<t(i))
            {
                while(l<=r&&kk(i,q[r])<=kk(i,j))--r;
                q[++r]=j;
            }
        }
    }
    put(f[m][n]);
    return 0;
}
View Code

技术图片

这道题呢 看完题目就觉得很难我甚至觉得dp不太好写 

硬写吧  f[i]表示前i个数字所取得的最小代价和 f[i]=min(f[i],f[j]+a[k]);//k j+1~i

直接转移 复杂度 n^3 因为 要求 a[k] j+1~i 之中的最大值

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
//f[i]表示前i个数字所取得的最小代价和
//f[i]=min(f[i],f[j]+a[k]);//k j+1~i
const int MAXN=100002;
int n,m,flag;
int a[MAXN],s[MAXN];
ll f[MAXN];
int q[MAXN],l,h;
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        s[i]=a[i]+s[i-1];
        if(a[i]>m)flag=1;
    }
    if(flag==1){put(-1);return 0;}
    for(int i=1;i<=n;i++)f[i]=INF*1000000ll;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<i;j++)
        {
            int maxx=0;
            for(int k=j+1;k<=i;k++)maxx=max(maxx,a[k]);
            if(s[i]-s[j]<=m)f[i]=min(f[i],f[j]+maxx);
        }
    }
    put(f[n]);
    return 0;
}
View Code

观察一下式子求最大值我完全可以采用ST表的算法预处理 然后 最终复杂度是n^2 因为要枚举j这个决策点 

但是既然是n^2的复杂度我完全可以 采用单调队列来寻找最大值吧 复杂度n^2 还是过不了TAT

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
//f[i]表示前i个数字所取得的最小代价和
//f[i]=min(f[i],f[j]+a[k]);//k j+1~i
const ll MAXN=100002;
ll n,m,flag;
ll a[MAXN],s[MAXN];
ll f[MAXN];
ll q[MAXN],l,r;
int main()
{
    freopen("1.in","r",stdin);
    n=read();m=read();
    for(ll i=1;i<=n;i++)
    {
        a[i]=read();
        s[i]=a[i]+s[i-1];
        if(a[i]>m)flag=1;
    }
    if(flag==1){put(-1);return 0;}
    for(ll i=1;i<=n;i++)f[i]=INF*1000000ll;
    for(ll i=1;i<=n;i++)
    {
        l=r=0;l=1;
        for(ll k=1;k<=i;k++)
        {
            while(l<=r&&a[k]>=a[q[r]])r--;
            q[++r]=k;
        }
        for(ll j=0;j<i;j++)
        {
            while(q[l]<=j)l++;
            if(s[i]-s[j]<=m)f[i]=min(f[i],f[j]+a[q[l]]);
        }
    }
    put(f[n]);
    return 0;
}
View Code

然 考虑终极优化 争取优化到O(n) 或者是 nlogn 不会啊 !

书上是这样说的 对于j(0<=j<i)可能成为最优决策除了需要满足 a[i]-a[j]<=M 还需要满足以下两个条件之一:

1 a[j]=max(a[k])(j<=k<=i) 2 j~i C[i]-C[j]<=M 的最小j

如果两个条件都不满足的话 那么 aj<max(ak) j<=k<=i && j~i C[i]-C[k]<=M

(j<=k<=i)max(a[k])==(j+1<=k<=i)max(a[k]) f[i-1]<=f[j] f[j-1]+max(ak j~i )<=f[i]+max(ak j+1~i)

那么此时 j-1 比 j 更有可能成为最优决策 此时j不可能成为最优决策

所以我们可以维护一个 最早的j 剩下的j 就只能是 单调递增 aj的值单调递减 的东西了 这仅仅是我们从上述两个结论中得来的结论。

那么 这也仅仅是单调性的优化 也就是说一些无用决策将被我们删掉 最后我们只需要 比较这么多单调递减的f[j]+a[k](j+1~i)哪个最小即可 

根据以上原理 我们可以筛选出很多不必要的决策 将这些决策舍弃

然后维护单调队列即可 但是 针对状态转移时 针对队列中的每个f值 我们都不知道哪个是最优的所以需要比较。

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
#define id(x) q[x].id
#define v(x) q[x].v
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
//f[i]表示前i个数字所取得的最小代价和
//f[i]=min(f[i],f[j]+a[k]);//k j+1~i
const ll MAXN=100002;
ll n,m,flag;
ll a[MAXN];
ll f[MAXN];
struct wy
{
    ll id,v;
}q[MAXN];
ll sum,t,l,r;
int main()
{
    freopen("1.in","r",stdin);
    n=read();m=read();
    for(ll i=1;i<=n;i++)
    {
        a[i]=read();
        if(a[i]>m)flag=1;
    }
    if(flag==1){put(-1);return 0;}
    for(ll i=1;i<=n;i++)
    {
        sum+=a[i];
        while(sum>m)sum-=a[t++];
        while(l<=r&&id(l)<t)l++;
        while(l<=r&&a[i]>=v(r))r--;
        id(++r)=i;
        v(r)=a[i];
        f[i]=f[t-1]+v(l);
        for(ll j=l;j<r;j++)f[i]=min(f[i],f[id(j)]+v(j+1));
    }
    //for(long long i=1;i<=n;i++)cout<<f[i]<<‘ ‘;
    put(f[n]);
    return 0;
}
View Code

这样的话可以直接比较 队列中的某个值 进行状态转移这道题真的很有意思 我想了1天呢。

然后貌似这样的算法是 n^2的 (玄学)比n^2小的多的多 所以可以A掉这道题但是...

为了追求更快的AC我们可以采用set维护一下单调队列中的最小值实现复杂度nlogn 

但是这个竟然没 上面那个貌似是n^2的算法要慢的多核心思想找到 最优决策。

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
//f[i]表示前i个数字所取得的最小代价和
//f[i]=min(f[i],f[j]+a[k]);//k j+1~i
const ll MAXN=100002;
ll n,m,flag;
ll a[MAXN];
ll f[MAXN];
ll q[MAXN],l,r,sum,t;
multiset<ll>s;
multiset<ll>::iterator it;
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(ll i=1;i<=n;i++)
    {
        a[i]=read();
        if(a[i]>m)flag=1;
    }
    if(flag==1){put(-1);return 0;}
    for(ll i=1;i<=n;i++)
    {
        sum+=a[i];
        while(sum>m)sum-=a[t++];
        while(l<=r&&q[l]<t)
        {
            if(s.empty()==1){l++;continue;}
            if((it=s.find(f[q[l]]+a[q[l+1]]))!=s.end());s.erase(it);
            l++;
        }
        while(l<=r&&a[i]>=a[q[r]])
        {
            if(s.empty()==1){r--;continue;}
            if((it=s.find(f[q[r-1]]+a[q[r]]))!=s.end())s.erase(it);
            r--;
        }
        if(l<=r)s.insert(f[q[r]]+a[i]);
        q[++r]=i;
        f[i]=f[t-1]+a[q[l]];
        if(s.begin()!=s.end())f[i]=min(f[i],*s.begin());
    }
    //for(ll i=1;i<=n;i++)cout<<f[i]<<‘ ‘<<endl;
    put(f[n]);
    return 0;
}
View Code

终于A掉了这道题好题啊 ,这个单调性优化关键是发觉上面的两个引理 也就是单调性的特点 进而优化可选择的决策空间 进而缩短时间。

下面是多重背包的单调性优化 :

首先是 朴素的多重背包 

完全当成01背包来写 这个就比较简单了。当时我以为这个复杂度不高的,寒假郑州的集训一道题两次多重背包,

没想到我T飞了。。。

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
//f[i]表示前i的空间能拿到的最大价值
const int MAXN=5002;
int n,m;
int w[MAXN],sum[MAXN],v[MAXN];
int f[MAXN];
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        sum[i]=read();
        w[i]=read();
        v[i]=read();
    }
    for(int i=1;i<=n;i++)
    {
        for(int k=1;k<=sum[i];k++)
        {
            for(int j=m;j>=w[i];j--)
            f[j]=max(f[j],f[j-w[i]]+v[i]);
        }
    }
    put(f[m]);
    return 0;
}
View Code

当然 有更优秀的解法 2进制拆分 主要原理是 2^0+2^1+...+2^p+R(剩下来的余数)当然其中p是能达到的最大的p

这一堆数字能拼成任意数字 对其在做01背包的情况下 就能快速得到最优解。

证明: 2^0+2^1+...+2^p 能拼成 任意 m-R之中的任意数字 毋庸置疑想想二进制的表示法。2^0+2^1+...2^p==m-R

而对于 R 因为R<2^p+1 且2^0+2^1+..2^p==2p+1 所以R<=2^0+2^1+...2^p

所以 这堆数字又可以拼成 1~R任意的数字 所以 综上 它是可以拼出1~m之中的任意数字 对每个数字都做一次01背包即可得到最优解

因为是最优解一定会是最优解(犹如是金子总会发光发热 神说要有光嘛)

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
//f[i]表示前i的空间能拿到的最大价值
const int MAXN=5002;
int n,m;
int w[MAXN],sum[MAXN],v[MAXN];
int f[MAXN];
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        sum[i]=read();
        w[i]=read();
        v[i]=read();
    }
    for(int i=1;i<=n;i++)
    {
        if(sum[i]*w[i]>=m)
            for(int j=w[i];j<=m;j++)
            f[j]=max(f[j],f[j-w[i]]+v[i]);
        else
        {
            int s=1,cnt=sum[i];
            while(s<=cnt)
            {
                for(int j=m;j>=s*w[i];j--)
                f[j]=max(f[j],f[j-s*w[i]]+s*v[i]);
                cnt-=s;s=s<<1;
            }
            for(int j=m;j>=cnt*w[i];j--)
                f[j]=max(f[j],f[j-cnt*w[i]]+cnt*v[i]);
        }
    }
    put(f[m]);
    return 0;
}
View Code

下面是一个单调队列优化的思路 要实现这个我们需把状态转移方程变一变。

f[j]表示前j空间能装的最大价值 f[j]=max{f[j-k*w[i]]+k*v[i]};

由于每次转移 只有倍数之间才会互相转移所以可以写成这样

f[u+p*w[i]]=max{f[u+k*w[i]]+(p-k)*v[i]};

这样使其具有单调性 然后利用单调队列维护最优决策即可 。

技术图片
//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<utility>
#include<queue>
#include<deque>
#include<vector>
#include<stack>
#include<algorithm>
#include<set>
#include<bitset>
#include<map>
#define INF 2147483646
#define ll long long
#define ldb long double
using namespace std;
char buf[1<<15],*ft,*fs;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<0||ch>9){if(ch==-)f=-1;ch=getc();}
    while(ch>=0&&ch<=9){x=x*10+ch-0;ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?x=-x,putchar(-):0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+0,x/=10;
    num==0?putchar(0):0;
    while(num)putchar(ch[num--]);
    putchar(10);return;
}
//f[i]表示前i的空间能拿到的最大价值
const int MAXN=5002;
int n,m;
int w[MAXN],sum[MAXN],v[MAXN];
int f[MAXN];
int k(int i,int u,int p){return f[u+p*w[i]]-p*v[i];}
int q[MAXN],l,r;
int main()
{
    freopen("1.in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        sum[i]=read();
        w[i]=read();
        v[i]=read();
    }
    for(int i=1;i<=n;i++)
    {
        for(int u=0;u<w[i];u++)
        {
            l=1;r=0;
            int p=(m-u)/w[i];
            for(int j=p-1;j>=max(p-sum[i],0);j--)
            {    
                while(l<=r&&k(i,u,q[r])<=k(i,u,j))r--;
                q[++r]=j;
            }
            for(int j=p;j>=1;j--)
            {
                while(l<=r&&q[l]>j-1)l++;
                f[u+j*w[i]]=max(f[u+j*w[i]],k(i,u,q[l])+j*v[i]);
                if(j-sum[i]-1>=0)
                {
                    while(l<=r&&k(i,u,q[r])<=k(i,u,j-sum[i]-1))r--;
                    q[++r]=j-sum[i]-1;
                }
            }
        }
    }
    put(f[m]);
    return 0;
}
View Code

多看 多想 多实践!

以上是关于dp 单调性优化总结的主要内容,如果未能解决你的问题,请参考以下文章

决策单调性优化dp

dp单调性优化

CF1293B - JOE is on TV! DP 序列DP 单调性优化

单调队列以及单调队列优化DP

BZOJ1010单调性DP优化

DP 斜率优化题目/决策单调性题目