数位DP
技术难度不大,但特点是细节多、情况多,很容易出错。同时,数位dp题往往可以用对拍的方法检验。
通常的模板是:
ll dp[int i1][int i2]...[int in]
i1...in可以是pos(数位),pre(前一位),zero(前导零)等。存储的是可以公用的、每一位都没有限制的量。
int a[int maxn]
以数组格式储存数
int len
数的长度
ll solve(ll x)
将数转换为数组格式,并在内部调用dfs函数并返回
ll dfs(int i1,int i2,...,int in)
核心,模拟填数,用dp数组记忆化搜索。注意要不要考虑前导零。
ll num;
int a[20],len;
ll dp[2][20];
ll dfs(int pos,bool pre,bool lim,...)
{
int Max=lim?a[pos]:9;
if(pos==0)return ...;
if(!lim&&dp[...]!=-1)return dp[...];
ll ret=0;
for(int i=0;i<=Max;++i)
{
//lim -> lim&&i==Max;
}
if(!lim)dp[...]=ret;
return ret;
}
ll solve(ll x)
{
len=0;
while(x)
{
a[++len]=x%10;
x/=10;
}
return dfs(len,0,1,...);
}
区间DP
二维,暴力
例子
四边形优化
若要处理形如$$dp(i,j)=min_{ileq kleq j-1}{dp(i,k)+dp(k+1,j)}+w(i,j,k)$$的状态转移方程,平凡的办法是按区间长度从小到大、左端点从小到大的顺序暴力枚举,复杂度为(O(n^3))。
如果区间的分割点具有某种单调性,即如果设(k(i,j))使(dp(i,k)+dp(k+1,j))取到最值,而成立(k(i',j)leq k(i,j))和(k(i,j)leq k(i,j')),其中(i'leq ileq jleq j'),那么,通过维护(k(i,j))可以将复杂度降至(O(n^2))。
这是因为:当枚举到区间长度为L时,维护(k(i,j))的时间为((k(2,L+1)-k(1,L))+...+(k(n-L+1,n)-k(n-1,n-L))leq n)
该单调性等价于(dp(i,j))满足四边形不等式:(dp(a,c)+dp(b,d)leq dp(b,c)+dp(a,d),forall aleq bleq cleq d)。这也是该优化被称为四边形优化的原因(不知道为什么叫四边形不等式,形式上与四边形对边和小于对角线和相似?)
至于如何判断(dp(i,j))是否满足四边形不等式,只要验证不等式(dp(i,j+1)-dp(i,j)leq dp(i-1,j+1)-dp(i-1,j))对全部i,j成立即可。
这是因为:设(aleq bleq cleq dleq e),由(dp(a,c)+dp(b,d)leq dp(b,c)+dp(a,d))和(dp(a,d)+dp(b,e)leq dp(b,d)+dp(a,e))可以推出(dp(a,c)+dp(b,e)leq dp(b,c)+dp(a,e))
另外,普通区间DP数据范围小,而可以四边形优化的区间DP数据范围大。(也可据此猜)
斜率优化
一维
若要处理形如$$dp(i)=min_{1leq jleq i-1}{dp(j)+w_i(j)}$$的状态转移方程,平凡的办法是按(i)从小到大、(j)从小到大的顺序暴力枚举,复杂度为(O(n^2))。
一如四边形优化试图预先确定最优分割点的范围,我们希望在这里也能找到最优“分割点”的范围,从而降低复杂度。换一种角度考虑,我们把求(dp(i))看作对(R^2)上的一系列点((dp(j),w_i(j)))做线性规划。如果(w_i(j))只与(j)有关,即(w_i(j)=w(j)),那么,如果已经求出了((dp(1),w(1)),...,(dp(i-1),w(i-1))),那么只要知道(F(x,y)=x+y)在这些点上取值的最小值即可求出(dp(i))。而由线性函数在凸形的顶点取到最值知,只要维护前(i-1)个点的凸包即可。更进一步地,该凸形斜率最接近-1的边的端点最有可能是最优点,因此可以二分求出这个最优点。复杂度为(O(nlog(n)))?
要求(w_i(j))只与(j)有关很多时候是一种奢望。然而,在实际问题中,(w_i(j))通常可以写成(f(i)g(j))的形式。这时,只要取(F(x,y)=x+f(i)y)即可类似地求解,而不必每次都重新算一遍凸包。
概率DP,数学期望
数学期望
求出数学期望的具体表达式有时是困难的,但写出递推式却是容易的,这个过程很像是DP。递推式往往用到期望的线性性。
有些题目要求树上的数学期望,这时直接按照题意写出的递推式很可能是循环定义的,即可能出现(dp(i)=f(dp(j),...),dp(j)=f(dp(i),...))的情况。解决方法是想方设法赋予一个单向的结构,即调整(dp(i))的表达式,使之只与(i)的父节点或只与(i)的子节点有关。
例子
本来还想记录一下矩阵优化,突然发现网上有一篇博客已经总结的非常好了。戳