window.cnblogsConfig = { homeTopImg: [ "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png", "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png" ], }

DP优化技巧

感谢https://www.luogu.com.cn/user/249973#main老师。

DP优化技巧

矩阵优化DP

1.矩阵快速幂(优化dp)

2.四边形不等式优化dp(a,b,c,d)(ac+bd<=ad+bc)

3.数据结构优化dp(线段树)

4.单调队列、二分栈优化dp

5.斜率优化dp

矩阵定义

矩阵乘法(更常见),矩阵加法

矩阵加法

一般式:\(C_{i,j}=A_{i,j}+B_{i,j}\)

其中\(A,B\) 均为\(N*M\) 矩阵,矩阵\(A+B\) 得到\(N*M\) 矩阵\(C\)

式子的含义为:矩阵\(C\)是由\(A,B\)对应位置上数值相加得到

注意:矩阵加法满足交换律

矩阵乘法

一般式:\(C_{i,j}=\sum\limits_{k=1}^K (A_{i,k}*B_{k,j})\) ,其中\(A\)\(N*K\) 的矩阵,\(B\)\(K*M\)的矩阵,矩阵\(A*B\)得到\(N*M\)的矩阵\(C\)

式子的含义为:矩阵\(C_{i,j}\)由矩阵\(A\)\(i\)行上的第\(K\)个数与矩阵\(B\)上的第\(j\)列上的第\(K\)个数分别相乘并求和得到

注意:矩阵乘法不满足交换律,满足结合律,满足分配率

前缀和优化dp

当转移遇到\(f_i = \sum_\limits{l<=j<=r}g_j\) ,就可以对\(g_j\)做一个前缀和,之后两端点差分

求和式可以转换成\(min,max\)

two-pointer优化dp

最常见的形式是“尺取法”。大概就是一个指针移动时,取最优值的另一个端点是单调移动的

我想在一个有序数组\(a\)中找到两个数\(a_i,a_j(i<j)\),使得\(a_j-a_i<=C\)\(a_j-a_i\)最大

当我们移动左端点\(i\)时,我们只需要找到\(a_j<=C+a_i\)的最大值即可,因为数组是有序的,所以\(j\)可以单调移动

本质就是固定一端点,最优化地移动另一个端点

这样我们就能省去其中一维的枚举,达到优化的目的

决策单调性对一类 1D/1D DP的优化

这里的xD/yD Dp的含义,是指的状态数由\(n^x\)种,每个状态的转移有\(n^y\)种的DP,而1D/1D Dp通常长这样

\[f_i=min(f_j+w(i,j)) \]

而决策单调性是指,对于两个决策点\(j,k(j<k)\),如果对于状态i,决策k优于j,那么对于任意的\(i'>i\),都有决策点k优于j,即从k转移一定更优,j一定不会再成为最优决策,决策是单调的,跟双指针优化dp类似的

根据\(w(i,j)\)的不同性质,我们可以采取不同的优化方法

w(i,j)只包含i和j的项->单调队列优化

不妨将转移写成\(f_i=min(max)(f_j+a[i]+b[j])\) 以min为例

因为\(a_i\)和j无关,所以实际转移式\(f_i=a_i+min(max)(f_j+b[j])\) ,所以我们只需要找到括号里最小的就可以

如果\(i-j<=K\),那我们可以维护一个单调队列,储存的元素\(f_j+b[j]\),当前假设我们枚举到i,这时候我们将要淘汰的元素就是\(j>=i-K\),(老且弱),我们只需要判断单调队列队首元素的下标,把不合法的情况删去即可,删去不合法的元素后,当前的队首就是合法的最小值,更新\(f_i\)即可

w(i,j)只含有i,j和ij的项->斜率优化

不妨将转移写成\(f_i=min(max)(a[i]*b[j]+c[i]+d[j])\) ,式中不含\(f_j\)是因为\(f_j\)可能参与了b[j]和d[j]的运算,隐去方便讨论且不影响后续结果,还是以min为例

现在需要的就是对于每个i,找到最优决策点j.对于每个i,只和j有关的是变量.于是我们将原式变形

\(-d[j]=a[i]*b[j]+c[i]-f[i]\)

我们可以用线性规划来解决这个问题.用一次函数\(y=kx+b\)来描述这个过程.那么\(y=-d[j],x=b[j],k=a[i],b=c[i]-f[i]\)

现在我们需要\(f_i\)尽可能的小,显然我们就是需要\(b=c[i]-f[i]\)尽可能的大.于是我们对于\([1,i-1]\)之间的所有的j,把\((b[j],-d[j])\)刻画在坐标轴上,再用\(y=a[i]x+b\)去做线性规划

显然需要截距最大时,直线一定切于这些点的上凸包上,所以我们只需要维护一个上凸包即可

而如何在i不断枚举时,在不断有新的点加入的情况下去维护这一个上凸包呢?之前提出问题的时候已经说明了,\(b[j]\)是单调递增的,也就是说当我的循环变量往右枚举时,新增的点总会出现在所有之前的点的右边.所以我们可以开一个单调队列去一次存下凸包上的所有的点,单调队列中相邻两点斜率是单调递减的.所以如果出现如图所示的情况,那么就把队尾的点删掉,因为它不可能成为决策最优点.

删除之后重复这个过程,直到不出现这个情况

如图

而即使维护了这个上凸包,若还是利用遍历上凸包上的点来寻找最优决策点的话依旧是不可以的.如果我们能保证a[i]是单调递减,于是我们需要决策单调性优化,如果说i的最优决策点是j,那么显然任意取\(i'>=i\),i'的最优决策点k一定满足\(k>=j\)

并且对于最优决策点,记它和之前的一个点的斜率为k1,它和之后的一个决策点是k2,需要满足\(k_1>a[i]>k_2\) ,因此每当作决策前,我们先判断队首和次队首的斜率是否大于\(a[i]\) ,如果是,队首出队,直到队首和次队首的连线斜率小于\(a[i]\),自此队首元素就是对于i的最优决策点

综上,每个点都最多只会进出队列一次,复杂度\(O(n)\)

回到最初的问题,究竟哪些性质的\(a,b\)可以用决策单调性的斜率优化,并且原因是什么呢?下面将给出一个更为普适的定理

决策单调性适用的定理--四边形不等式与决策单调性

在解释斜率优化那部分的遗留问题之前,我们需要对决策单调性这一整部分进行归纳总结

四边形不等式:对于任意的\(a<=b<=c<=d\),有\(w(a,d)+w(b,c) >=w(a,c)+w(b,d)\)

四边形不等式并不等同于代数形式的不等式,它只是一些w的二元函数具备的特殊性质

定理:若w(i,j)满足四边形不等式,那1D/1D DP是可以用决策单调性优化的.

证明:

以优化的第一类式子为例,即\(f_i=min(f_j+w(i,j))\) ,并且w满足\(w(a,d)+w(b,c)>=w(a,c)+w(b,d)\)

\(p_i\)表示i的最优决策点是\(p_i\),那么有

\(f_{p_i}+w(p_i,i)<=f_j+w(j,i)\) (1)

同时,我们需要证明的式子是

\(f_{p_{i+1}}+w(p_{i+1},i+1)<=f_j+w(j,i+1)\)(2)

把(2)式中的j变成\(p_i\)

\(f_{p_{i+1}}+w(p_{i+1},i+1)<=f_{p_i}+w(p_i,i+1)\)(3)

分情况讨论

1.\(p_{i+1}=i\),那么显然\(p_{i+1}=i>=p_i\),满足决策单调性

2.\(p_{i+1}!=i\) ,那么把(1)式中的j变成\(p_{i+1}\)

\(f_{p_i}+w(p_i,i)<=f_{p_{i+1}}+w(p_{i+1},i)\)(4)

(3)+(4)得

\(w(p_{i+1},i+1)+w(p_i,i)<=w(p_i,i+1)+w(p_{i+1},i)\)

如果\(p_i>p_{i+1}\),代入上式,不等号和我们已知的不等号相反,显然矛盾.因此\(p_i<=p_{i+1}<=i<=i+1\)

综上\(p_i<=p_{i+1}\),即满足决策单调性

模板

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9+7;
int n,k;
struct matrix{
	int a[105][105];
	matrix operator*(const matrix & b) const {//重载乘法符号
		matrix ret;
		for(int i = 1;i <= n;i++){
			for(int j = 1;j <= n;j++){
				for(int k = 1;k <= n;k++){
					ret.a[i][j] = (ret.a[i][j]+(a[i][k]*b.a[k][j]%mod)) % mod;
				}
			}
		}
		return ret;
	}
	matrix operator+(const matrix &b) const{//重载加法符号
		matrix ret;
		for(int i = 1;i <= n;i++){
			for(int j = 1;j <= n;j++){
				ret.a[i][j] = (a[i][j] + b.a[i][j]) % mod;
			}
		}
		return ret;
	}
	matrix(){
		memset(a,0,sizeof a);
	}
	
};
matrix qpow(matrix a,int x){//矩阵快速幂
	matrix ret;
	for(int i = 1;i <= n;i++) ret.a[i][i] = 1;
	while(x){
		if(x & 1) ret=ret*a;
		a=a*a;
		x>>=1;
	}
	return ret;
}
int k2;
signed main(){
	cin >> n >> k2;
	matrix a;
	for(int i = 1;i <= n;i++){
		for(int j = 1;j <= n;j++){
			scanf("%lld",&a.a[i][j]);
		}
	}
	a=qpow(a,k2);
	for(int i = 1;i <= n;i++){
		for(int j = 1;j <= n;j++){
			printf("%lld ",a.a[i][j]);
		}
		puts("");
	}
	return 0;
}

posted @ 2023-11-24 19:07  gsczl71  阅读(75)  评论(0编辑  收藏  举报