昨天忘记写了,今天带着昨天的份一起写了。
斜率优化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; }
第二道,[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; }
决策单调性优化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)); } }
第二道,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; }
嘿,总算是弄完了,哈~。接下来估计不会再做动规了,会复习或学习别的知识点。大家一起加油吧!