DP优化

学了好几个月DP就是没学优化,现在终于开始啃这块知识了...

很典型的DP题,显然我们可以设f[x]表示上一次跳到x的最大能量...

40分暴力:

#include<bits/stdc++.h>
#define max(a,b) (((a)>(b))?(a):(b))
using namespace std;
const int N=2001000;
int f[N],n,k,sum[N];
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}
int main()
{
    freopen("1.in","r",stdin);
    n=read();k=read();
    for(register int i=1;i<=n;++i) sum[i]=sum[i-1]+read();
    f[0]=k;
    for(register int i=1;i<=n;++i)
        for(register int j=0;j<i;++j) if(f[j]>=100*i) f[i]=max(f[i],f[j]+sum[i]-sum[j]-100*i);
    printf("%d\n",f[n]);
    return 0;    
}

那我们接下来就要考虑优化了,

f[i]=max(f[i],f[j]+sum[i]-sum[j]-i*100);
f[i]=max(f[j]-sum[j])+sum[i]-i*100;
保证f[j]-sum[j]最优...
思考对于i的决策x和y,假定x<y且都合法
那么如果f[x]-sum[x]>=f[y]-sum[y],我们这里只需在证明f[y]>f[x]即可证明决策具有单调性...
也就是说随着最优决策递增,答案也在递增...
将上式变形:f[x]-f[y]>=sum[x]-sum[y],这里显然sum[x]-sum[y]>0,所以f[x]-f[y]>0;
证毕.

#include<bits/stdc++.h>
#define max(a,b) (((a)>(b))?(a):(b))
using namespace std;
const int N=2001000;
int f[N],n,k,sum[N],q[N],head,tail;
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}
int main()
{
    freopen("1.in","r",stdin);
    n=read();k=read();
    for(register int i=1;i<=n;++i) sum[i]=sum[i-1]+read();
    f[0]=k;
    for(register int i=1;i<=n;++i)
    {
        while(head<=tail&&f[q[head]]<100*i) ++head;
        f[i]=f[q[head]]-sum[q[head]]+sum[i]-100*i;
        while(head<=tail&&f[i]-sum[i]>=f[q[tail]]-sum[q[tail]]) --tail;
        q[++tail]=i;
    }
    printf("%d\n",f[n]);
    return 0;    
}

 

这个题...

我们很显然要设f[i][j]表示前i个点且最后一个点高度为j时的最小状态,但高度太大了

之后我们发现修改后的高度只会与原高度相同,所以将数据离散...

暴力由此产生:

#include<bits/stdc++.h>
using namespace std;
const int N=2100;
int n,h[N],f[N][N],ans=1e9,ff[N][N];//f[i][j]表示前i个数且最后一个数高度为h[j]时的最小代价. 
vector<int>v1[N],v2[N];
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}
int main()
{
//    freopen("1.in","r",stdin);
    n=read();
    for(register int i=1;i<=n;++i) h[i]=read();
    memset(f,127,sizeof(f));memset(ff,127,sizeof(ff));
    for(register int i=1;i<=n;++i)
        for(register int j=1;j<=i;++j)
        {
            if(h[j]<=h[i]) v1[i].push_back(j);
            if(h[j]>=h[i]) v2[i].push_back(j);
        }
    for(register int i=1;i<=n;++i) ff[1][i]=f[1][i]=abs(h[1]-h[i]);
    for(register int i=2;i<=n;++i)
        for(register int j=1;j<=n;++j)
        {
            for(register int k=0;k<v1[j].size();++k) f[i][j]=min(f[i][j],f[i-1][v1[j][k]]+abs(h[i]-h[j]));
            for(register int k=0;k<v2[j].size();++k) ff[i][j]=min(ff[i][j],ff[i-1][v2[j][k]]+abs(h[i]-h[j]));
        }
    for(register int i=1;i<=n;++i) ans=min(ans,min(f[n][i],ff[n][i]));        
    printf("%d",ans);
    return 0;
} 

O(n3)肯定吃不消,考虑优化;

我们可以将h[i]排序,

for(register int i=2;i<=n;++i)
        for(register int j=1;j<=n;++j)
            for(register int k=1;k<=j;++k)
                f[i][j]=min(f[i][j],f[i-1][k]+abs(h[i]-t[j]));

代码就变成这样了,我们发现在k的枚举中abs(h[i]-t[j])与k无关,有关的只有f[i-1][k](k<=j),而这个就是我们优化的重点,我们可以用o[i][j]表示在阶段i,j时的最优决策;

那么只用在i时更新下一阶段的最优决策就可以省掉一个循环

#include<bits/stdc++.h>
using namespace std;
const int N=2100;
int n,h[N],f[N][N],ans=1e9,t[N],o[N][N];//f[i][j]表示前i个数且最后一个数高度为h[j]时的最小代价. 
inline int read()
{
    int x=0,ff=1;
    char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*ff;
}
int main()
{
    freopen("1.in","r",stdin);
    n=read();
    for(register int i=1;i<=n;++i) h[i]=read(),t[i]=h[i];
    sort(t+1,t+1+n);
    memset(f,127,sizeof(f));
    memset(o,127,sizeof(o));
    for(register int i=1;i<=n;++i) 
    {
        f[1][i]=abs(h[1]-t[i]);
        o[1][i]=min(o[1][i-1],f[1][i]);
    }
    for(register int i=2;i<=n;++i)
    {
        for(register int j=1;j<=n;++j) 
        {
            f[i][j]=o[i-1][j]+abs(h[i]-t[j]);
            o[i][j]=min(o[i][j-1],f[i][j]);
        }
    }
    for(register int i=1;i<=n;++i) ans=min(ans,f[n][i]);        
    printf("%d",ans);
    return 0;
} 

然而,这道题还有更优秀的贪心...

我们考虑一个递增序列,之后又来了一个小的数字;

设序列中的最大值为a,次大值为b,新来的数字为c,c<a,这时我们要花费代价至少(c-a);

考虑这些代价可以做出那些改变,可以将c与a两个数字改成(c,c),(c+1,c+1)...(a-1,a-1),(a,a);

而此时b一定在这些数里,我们要保证不下降,且尽量使最大的值尽可能小,只能将a和c都改成b;

而这用大根堆很容易维护,就不写代码了...

posted @ 2019-09-30 10:25  逆天峰  阅读(128)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//