zoukankan      html  css  js  c++  java
  • 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

    多看 多想 多实践!

  • 相关阅读:
    google protobuf
    spawn-fcgi和libfcgi源码解读
    [Linux] 查看进程的上下文切换pidstat
    [MySQL] update语句的redo log过程
    [转载] PHP 8新特性之JIT简介
    [PHP] 新浪企邮webmail在memcache实践使用共享session
    [Go] Golang练习项目-web客服系统即时通讯websocket项目go-fly
    [PHP] php8的jit不支持32位系统WARNING: JIT not supported by host architecture
    [PHP] 源码编译安装opcache
    [PHP] 查找使用的哪个配置文件php.ini
  • 原文地址:https://www.cnblogs.com/chdy/p/10490825.html
Copyright © 2011-2022 走看看