复习动规(4)

昨天忘记写了,今天带着昨天的份一起写了。

斜率优化DP

第一道,[HNOI2008]玩具装箱非常经典的斜率优化dp题。

首先定义f[x]为将前x件玩具刚好全部放入容器中的最小费用。

则状态转移方程即为f[i]=min{f[j]+(i-(j+1)+sum[i]-sum[j]-L)2}。(j<=i)

使p[i]=sum[i]+i,使L++,原方程可转化为f[i]=min{f[j]+(p[i]-p[j]-L)2}

展开括号,并将与j无关项移出大括号,得:f[i]=min{f[j]+p[j]2+2p[j]*L-2p[j]*p[i]}+p[i]2+L2-2p[i]*L

然后我们就可以设b=f[i],k=p[i],x=2p[j],y=f[j]+p[j]2+2p[j]*L(这里我们忽略大括号外面的项,计算时加上即可)

根据数形结合的思想,这题就变成了:图上有若干个点(x,y),每次有两个操作:

1.加入一个新点,这个点的x要大于之前的所有点。

2.给定一个斜率k,求过图上的点且斜率为k的直线的最小截距。

然后就可以用下凸包维护了。(最大截距就用上凸包)

说起来好像大部分斜率优化DP都是这样的,但是这题的k也是递增的,所以凸包用单调队列即可O(n)。

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e4+1e3;
int n,l,r; ll L,a[N],p[N],f[N];
struct nod{
    ll x,y;
}b[N],A;
int rd(){
    int s=0,ff=1;
    char w=getchar();
    while(!isdigit(w))
        ff^=w=='-',w=getchar();
    while(isdigit(w))
        s=s*10+w-'0',w=getchar();
    return ff?s:-s;
}
double Qiuk(nod X,nod Y){
    return 1.0*(X.y-Y.y)/(X.x-Y.x);
}
int main(){
    n=rd(); L=rd()+1;
    for(int i=1;i<=n;i++)
        a[i]=rd(),p[i]=p[i-1]+a[i];
    for(int i=1;i<=n;i++) p[i]+=i;
    l=1,r=0; b[++r].x=0; b[r].y=0;
    for(int i=1;i<=n;i++){
        while(l<r&&Qiuk(b[l+1],b[l])<=p[i]) l++;
        f[i]=b[l].y-p[i]*b[l].x+(p[i]-L)*(p[i]-L);
        A.x=2*p[i]; A.y=f[i]+p[i]*p[i]+2*p[i]*L;
        while(l<r&&Qiuk(A,b[r])<Qiuk(b[r],b[r-1])) r--;
        b[++r]=A;
    }
    printf("%lld
",f[n]);
    return 0;
}
[HNOI2008]玩具装箱

第二道,[NOI2019]回家路线这题要以列车的编号为下标。

首先设f[i]为第i辆列车到站后的最小烦躁值,方程即为:

f[i]=min{f[j]+A(pi-qj)2+B(pi-qj)+C+qi-qj}。(pi>=qj&&yj==xi

拆方程和求凸包相信大家都会,这里我讲下如何满足限制条件。

将上车和下车分为两个操作,上车时求f[i]的值,下车时将点放入单调队列。

因为时间小可以用vector直接放入桶排中,这样时间上从大到小去做就可以满足pi>=qj了。

yj==xi就可以再开n个vector作为单调队列,完美。

需要注意这题的点之间的x可能相等,所以求斜率时记得判断x相等的情况。

具体可以看我代码(代码可读性较差,望谅解):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+1e4,M=1e6+1e5;
int n,m,t,l[N],r[N];
ll A,B,C,f[M],ans=4e18;
struct nod{
    int x,y,w; ll p,q;
}T,a[M]; vector<nod>P[41000],PP[41000];
struct nodd{
    ll x,y;
}D; vector<nodd>Q[N];
int Max(int x,int y){return x>y?x:y;}
ll Min(ll x,ll y){return x<y?x:y;}
int rd(){
    int s=0,ff=1;
    char w=getchar();
    while(!isdigit(w))
        ff^=w=='-',w=getchar();
    while(isdigit(w))
        s=s*10+w-'0',w=getchar();
    return ff?s:-s;
}
double Qiuk(nodd X,nodd Y){
    if(X.x==Y.x){
        if(X.y>Y.y) return 1e18;
        else return -1e18;
    }
    return 1.0*(X.y-Y.y)/(X.x-Y.x);
}
int main(){
    n=rd(); m=rd(); A=rd(); B=rd(); C=rd();
    for(int i=1;i<=n;i++) l[i]=0,r[i]=-1;
    for(int i=1;i<=m;i++){
        a[i].x=rd(),a[i].y=rd(),a[i].p=rd(),a[i].q=rd(),a[i].w=i;
        P[a[i].p].push_back(a[i]); PP[a[i].q].push_back(a[i]);
        t=Max(t,(int)a[i].q);
    } D.x=0; D.y=0; ++r[1]; Q[1].push_back(D);
    memset(f,127,sizeof(f));
    for(int i=0;i<=t;i++){
        for(int j=0;j<PP[i].size();j++){ T=PP[i][j];
            if(f[T.w]>4e18) continue ;
            D.x=2*A*T.q; D.y=f[T.w]+A*T.q*T.q-B*T.q-T.q;
            while(l[T.y]<r[T.y]&&Qiuk(Q[T.y][r[T.y]],Q[T.y][r[T.y]-1])>=Qiuk(D,Q[T.y][r[T.y]])) r[T.y]--,Q[T.y].pop_back();
            ++r[T.y]; Q[T.y].push_back(D);
        }
        for(int j=0;j<P[i].size();j++){ T=P[i][j];
            while(l[T.x]<r[T.x]&&Qiuk(Q[T.x][l[T.x]+1],Q[T.x][l[T.x]])<=T.p) l[T.x]++;
            if(l[T.x]<=r[T.x]) f[T.w]=Q[T.x][l[T.x]].y-Q[T.x][l[T.x]].x*T.p+A*T.p*T.p+B*T.p+C+T.q;
        }
    }
    for(int i=1;i<=m;i++)
        if(a[i].y==n) ans=Min(ans,f[i]);
    printf("%lld
",ans); return 0;
}
[NOI2019]回家路线

决策单调性优化DP

一道,[NOI2009]诗人小G

乍一看方程为f[i]=min{f[j]+|i-(j+1)+sum[i]-sum[j]-L|P}。(j<=i)

我还以为又是斜率优化的板子,这和上面的方程几乎一样啊。

然后才发现哦吼完蛋不会这个有绝对值而且也拆不开,于是我们需要换个方法。

根据打表发现它的决策单调,于是就可以O(n*logn)过了。

数位DP

第一道,不要62个人偏好记忆化搜索做数位dp,记忆化搜索的数位dp推荐一篇很好的博客

emmmm不知道怎么讲,数位dp都是一个板子,大家看看代码应该就明白了。

#include<bits/stdc++.h>
using namespace std;
int ct,a[9],f[9][2];
int Dfs(int x,bool pre,bool lim){
    if(!x) return 1;
    if(!lim&&f[x][pre]!=-1) return f[x][pre];
    int p=(lim?a[x]:9),s=0;
    for(int i=0;i<=p;i++){
        if(i==4) continue ;
        if(pre&&i==2) continue ;
        s+=Dfs(x-1,i==6,lim&&(i==p));
    }
    if(!lim) f[x][pre]=s; return s;
}
int Query(int x){ ct=0;
    while(x) a[++ct]=x%10,x/=10;
    return Dfs(ct,0,1);
}
int main(){
    int l,r; memset(f,-1,sizeof(f));
    while(1){
        scanf("%d%d",&l,&r); if(!l&&!r) return 0;
        printf("%d
",Query(r)-Query(l-1));
    }
}
不要62

第二道,F(x)这题稍微特殊点,f[x][s]的s存的是还剩s个可以减去。代码就不放了。

第三道,[ZJOI2010]数字计数

对于0~9中的每个数,我们分别求其出现的次数,而且函数要多带个参数判断有无前导0。

第四道,[SCOI2014]方伯伯的商场之旅个人感觉这题比较难,完全不知道怎么对数位得到合并石子的最优策略。

应该是先把所有石子都合并到第一堆,这个很好求。但很明显不是最优状态,于是我们需要依次枚举最优决策点

因为决策点由一变到二,所以总值相当于减去2到n的总石子数且加上1的石子数,所以判断2到n的石子数减去1的石子数是否大于0就行了,大于0就可以让答案减去该值。

再就决策点由二变到三,总值相当于减去3到n的总石子数且加上1到2的石子数......如此反复即可。代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int K,ct,a[55];
ll f[55][1100];
ll Dfs1(int x,ll s,int lim){
    if(!x) return s;
    if(!lim&&f[x][s]!=-1)
        return f[x][s];
    int p=lim?a[x]:K-1; ll sum=0;
    for(int i=0;i<=p;i++)
        sum+=Dfs1(x-1,s+(ll)i*(x-1),lim&&i==p);
    if(!lim) f[x][s]=sum; return sum;
}
ll Dfs2(int x,ll s,int lim,int k){
    if(s<0) return 0;
    if(!x) return s;
    if(!lim&&f[x][s]!=-1)
        return f[x][s];
    int p=lim?a[x]:K-1; ll sum=0;
    for(int i=0;i<=p;i++){
        if(x>=k) sum+=Dfs2(x-1,s+i,lim&&i==p,k);
        else sum+=Dfs2(x-1,s-i,lim&&i==p,k);
    }
    if(!lim) f[x][s]=sum; return sum;
}
ll Query(ll x){
    ct=0; ll ans=0;
    while(x)
        a[++ct]=x%K,x/=K;
    memset(f,-1,sizeof(f));
    ans=Dfs1(ct,0,1);
    for(int i=2;i<=ct;i++){
        memset(f,-1,sizeof(f));
        ans-=Dfs2(ct,0,1,i);
    }
    return ans;
}
int main(){
    ll l,r;
    scanf("%lld%lld%d",&l,&r,&K);
    printf("%lld
",Query(r)-Query(l-1));
    return 0;
}
[SCOI2014]方伯伯的商场之旅

嘿,总算是弄完了,哈~。接下来估计不会再做动规了,会复习或学习别的知识点。大家一起加油吧!

原文地址:https://www.cnblogs.com/manmanjiangQwQ/p/13332965.html