dp题目整理

注:这是dp套路整理里面题的题解qwq

一、简单dp

1.1 快速幂优化dp

1.1.1 模板题 斐波那契数列

大家都知道,斐波那契数列是满足如下性质的一个数列:
F n = { 1                              ( n ≤ 2 ) F n − 1 + F n − 2           ( n > 2 ) F_n=\left\{ \begin{array}{lr} 1\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (n\le 2) \\ F_{n-1}+F_{n-2}\ \ \ \ \ \ \ \ \ (n>2) \\ \end{array} \right. Fn={1                            (n2)Fn1+Fn2         (n>2)
请你求出 F n   m o d   1 0 9 + 7 ( 1 ≤ n < 2 63 ) F_n\ \rm mod\ 10^9+7(1\le n<2^{63}) Fn mod 109+7(1n<263)的值。

构造出矩阵快速幂转移矩阵:
[ F n F n − 1 ] = [ 1 1 1 0 ] ∗ [ F n − 1 F n − 2 ] \begin{bmatrix}F_{n}\\F_{n-1}\end{bmatrix}=\begin{bmatrix}1 & 1\\1 & 0\end{bmatrix}*\begin{bmatrix}F_{n-1}\\F_{n-2}\end{bmatrix} [FnFn1]=[1110][Fn1Fn2]
于是有 [ F n F n − 1 ] = [ 1 1 1 0 ] n − 2 ∗ [ F 2 F 1 ] = [ 1 1 1 0 ] n − 2 ∗ [ 1 1 ] \begin{bmatrix}F_{n}\\F_{n-1}\end{bmatrix}=\begin{bmatrix}1 & 1\\1 & 0\end{bmatrix}^{n-2}*\begin{bmatrix}F_{2}\\F_{1}\end{bmatrix}=\begin{bmatrix}1 & 1\\1 & 0\end{bmatrix}^{n-2}*\begin{bmatrix}1\\1\end{bmatrix} [FnFn1]=[1110]n2[F2F1]=[1110]n2[11]
于是可以用矩阵快速幂求解 F n F_n Fn的值。
代码略

1.1.2 模板题 zyd的妹子其二

zyd要妥善安排他的后宫,他想在机房摆一群妹子,一共有 n n n个位置排成一排,每个位置可以摆妹子也可以不摆妹子。有些类型妹子如果摆在相邻的位置(隔着一个空的位置不算相邻),就不好看了。假定每种妹子数量无限,求摆妹子的方案数。
输入有 m + 1 m+1 m+1行,第一行有两个用空格隔开的正整数n、m,m表示妹子的种类数。接下来的 m m m行,每行有 m m m个0/1字符,第 i i i行第 j j j列为 a i j a_{ij} aij。若 a i j a_{ij} aij为1,则表示第 i i i种妹子第 j j j种妹子不能排在相邻的位置,输入保证对称。 n ≤ 1 0 9 , m ≤ 100 n\le 10^9,m\le 100 n109,m100

d p [ i ] [ j ] dp[i][j] dp[i][j]表示放了 i i i个妹子,最后一个妹子是 j j j的方案数。
d p [ i ] [ j ] = ∑ a j k = = 0 d p [ i − 1 ] [ k ] dp[i][j]=\sum_{a_{jk}==0} dp[i-1][k] dp[i][j]=ajk==0dp[i1][k]
b i j = 1 − a i j b_{ij}=1-a_{ij} bij=1aij
于是得到 [ d p [ i ] [ m ] d p [ i ] [ m − 1 ] d p [ i ] [ m − 2 ] . . . d p [ i ] [ 1 ] ] = [ b 11 b 12 b 13 . . . b 1 m b 21 b 22 b 23 . . . b 2 m b 31 b 32 b 33 . . . b 3 m . . . . . . . . . . . . . . . b m 1 b m 2 b m 3 . . . b m m ] ∗ [ d p [ i − 1 ] [ m ] d p [ i − 1 ] [ m − 1 ] d p [ i − 1 ] [ m − 2 ] . . . d p [ i − 1 ] [ 1 ] ] \begin{bmatrix}dp[i][m]\\dp[i][m-1]\\dp[i][m-2]\\...\\dp[i][1]\end{bmatrix}=\begin{bmatrix}b_{11} & b_{12} & b_{13} & ... & b_{1m}\\b_{21} & b_{22} &b_{23} & ... & b_{2m}\\b_{31} & b_{32} & b_{33} & ... & b_{3m}\\ ... & ... & ... & ... & ...\\ b_{m1} & b_{m2} & b_{m3} & ... & b_{mm}\end{bmatrix}*\begin{bmatrix}dp[i-1][m]\\dp[i-1][m-1]\\dp[i-1][m-2]\\...\\dp[i-1][1]\end{bmatrix} dp[i][m]dp[i][m1]dp[i][m2]...dp[i][1]=b11b21b31...bm1b12b22b32...bm2b13b23b33...bm3...............b1mb2mb3m...bmmdp[i1][m]dp[i1][m1]dp[i1][m2]...dp[i1][1]
代码和剩下的步骤略。

1.2 LIS & LCS的优化

1.2.1 模板题 LIS

给定一长度为 n n n的数列,请在不改变原数列顺序的前提下,从中随机的取出一定数量的整数,并使这些整数构成单调上升序列。 输出这类单调上升序列的最大长度。 n ≤ 1 0 5 n\le 10^5 n105

f i = max ⁡ j = 1 i − 1 { f j + 1 } , a j < a i f_i=\max_{j=1}^{i-1}\{f_j+1\},a_j<a_i fi=maxj=1i1{fj+1},aj<ai。离散化后用树状数组记录下 ≤ x \le x x f f f的最大值,遍历求解。

二、背包

2.1 01背包(略)

2.2 完全背包(略)

2.3 多重背包

2.3.1 模板题 宝物筛选

小 FF 找到了王室的宝物室,里面堆满了无数价值连城的宝物。
小 FF 对洞穴里的宝物进行了整理,他发现每样宝物都有一件或者多件。他粗略估算了下每样宝物的价值,之后开始了宝物筛选工作:小 FF 有一个最大载重为 W W W 的采集车,洞穴里总共有 n n n 种宝物,每种宝物的价值为 v i v_i vi,重量为 w i w_i wi,每种宝物有 m i m_i mi 件。小 FF 希望在采集车不超载的前提下,选择一些宝物装进采集车,使得它们的价值和最大。 n ≤ 100 , ∑ m i ≤ 1 0 5 , 0 ≤ W ≤ 4 ∗ 1 0 4 n\le 100,\sum m_i\le 10^{5},0\le W\le 4*10^4 n100,mi105,0W4104

d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i种宝物,目前载重为 j j j的最大价值。
转移方程: d p [ i ] [ j ] = max ⁡ { d p [ i − 1 ] [ j − k ∗ w i ] + k ∗ v i } dp[i][j]=\max\{dp[i-1][j-k*w_i]+k*v_i\} dp[i][j]=max{dp[i1][jkwi]+kvi}
注意到对于每个 i i i j j j j − k ∗ w i j-k*w_i jkwi是模 w i w_i wi同余的。也就是说 j j j只会被与 j j j w i w_i wi相同的数影响。
j = p ∗ w i + r j=p*w_i+r j=pwi+r,则转移方程可化为 d p [ i ] [ j ] = max ⁡ { d p [ i − 1 ] [ k ∗ w i + r ] + ( p − k ) ∗ v i } ( p − m i ≤ k ≤ p ) dp[i][j]=\max\{dp[i-1][k*w_i+r]+(p-k)*v_i\}(p-m_i \le k\le p) dp[i][j]=max{dp[i1][kwi+r]+(pk)vi}(pmikp)
拆开 p ∗ v i p*v_i pvi这项常数,得到 d p [ i ] [ j ] = max ⁡ { d p [ i − 1 ] [ k ∗ w i + r ] − k ∗ v i } + p ∗ v i ( p − m i ≤ k ≤ p ) dp[i][j]=\max\{dp[i-1][k*w_i+r]-k*v_i\}+p*v_i(p-m_i\le k\le p) dp[i][j]=max{dp[i1][kwi+r]kvi}+pvi(pmikp)
发现这就是一个滑块窗口,所以用单调队列乱搞。

#include<stdio.h>
#include<cstring>
#include<algorithm>
#define re register int 
using namespace std;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=100005;
int n,V,v[Size],w[Size],num[Size],dp[Size];
int hd,tl;
struct node {
	int id,val;
} Queue[Size];
inline void push(int id,int val) {
	while(hd<=tl && val>Queue[tl].val)	tl--;
	Queue[++tl].id=id;
	Queue[tl].val=val;
}
inline void pop(int id,int num) {
	while(hd<=tl && Queue[hd].id+num<id)	hd++;
}
int main() {
	n=read();
	V=read();
	int ans=0;
	for(re i=1; i<=n; i++) {
		v[i]=read();
		w[i]=read();
		num[i]=read();
		if(!w[i]) {
			n--;
			ans+=v[i]*num[i];
		}
	}
	for(re i=1; i<=n; i++) {
		for(re r=0; r<w[i]; r++) {
			int lim=(V-r)/w[i];
			hd=1,tl=0; 
 			for(re p=0; p<=lim; p++) {
				push(p,dp[p*w[i]+r]-p*v[i]);
				pop(p,num[i]);
				dp[p*w[i]+r]=max(dp[p*w[i]+r],Queue[hd].val+p*v[i]);
			}
		}
	}
	printf("%d",dp[V]);
	return 0;
}

2.3.2 例题 shopping

马上就是小苗的生日了,为了给小苗准备礼物,小葱兴冲冲地来到了商店街。商店街有 n n n个商店,并且它们之间的道路构成了一颗树的形状。
i i i个商店只卖第 i i i种物品,小苗对于这种物品的喜爱度是 w i w_i wi,物品的价格为 c i c_i ci,物品的库存是 d i d_i di。但是商店街有一项奇怪的规定:如果在商店 u , v u,v u,v买了东西,并且有一个商店 w w w u u u v v v的路径上,那么必须要在商店 w w w买东西。小葱身上有 m m m元钱,他想要尽量让小苗开心,所以他希望最大化小苗对买到物品的喜爱度之和。这种小问题对于小葱来说当然不在话下,但是他的身边没有电脑,于是他打电话给同为OI选手的你,你能帮帮他吗? n ≤ 500 , m ≤ 4000 , w i ≤ 4000 , d i ≤ 100 n\le 500,m\le 4000,w_i\le 4000,d_i\le 100 n500,m4000,wi4000,di100

若在 u , v u,v u,v买了东西,那么 u , v u,v u,v路径之间的所有点都要买东西,也就是说买东西的节点构成了树上连通块。果断用点分治。
题目显然是一个多重背包,因此用dfs序转移+单调队列优化多充背包即可。
代码懒得写……

2.4 分组背包

2.4.1 例题 [HNOI2007]梦幻岛宝珠

给你N颗宝石,每颗宝石都有重量和价值。要你从这些宝石中选取一些宝石,保证总重量不超过W,且总价值最大,并输出最大的总价值。数据范围: N ⩽ 100 , W ⩽ 2 30 N⩽100,W⩽2^{30} N100,W230。每颗宝石的重量可以表示为 a ∗ 2 b ( a ⩽ 10 ; b ⩽ 30 ) a*2^b(a\leqslant 10;b\leqslant 30) a2ba10;b30

直接背包显然容量太大了,注意到保证每颗宝石的重量可以表示为 a ∗ 2 b ( a ≤ 10 , b ≤ 30 ) a*2^b(a\le10,b\le30) a2b(a10,b30)的形式,考虑把 b b b相同的宝石分为一组,进行分组背包。
假设第 i i i组宝石的重量都可以表示为 a ∗ 2 i a*2^i a2i d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i组宝石,且所用重量不超过 j ∗ 2 i j*2^{i} j2i得到的最大价值。
先做一次dp预处理出组内子状态信息。然后组见合并的dp方程: d p [ i ] [ j ] = max ⁡ { d p [ i ] [ j − k ] + d p [ i − 1 ] [ m i n ( s u m [ i − 1 ] , 2 k + ( W 2 i − 1 & 1 ) ) ] } dp[i][j]=\max\{dp[i][j-k]+dp[i-1][min(sum[i-1],2k+(\large\frac{W}{2^{i-1}}\normalsize \&1))]\} dp[i][j]=max{dp[i][jk]+dp[i1][min(sum[i1],2k+(2i1W&1))]}
后面这一串的意义:第 i i i组用了 ( j − k ) ∗ 2 i (j-k)*2^i (jk)2i的重量,则第 i − 1 i-1 i1组用了 k ∗ 2 i = 2 k ∗ 2 i − 1 k*2^i=2k*2^{i-1} k2i=2k2i1的重量。但如果只算 d p [ i − 1 ] [ 2 k ] dp[i-1][2k] dp[i1][2k],最后的总重量实际上是 2 ⌊ l o g 2 W ⌋ 2^{\lfloor log_2W\rfloor} 2log2W的,所以应该要在中间加上 W W W后面二进制位的重量。而 W 2 i − 1 & 1 \large\frac{W}{2^{i-1}}\normalsize \&1 2i1W&1表示的是 W W W的第 i − 1 i-1 i1位的值。加上这一位转移,最后的答案就是 d p [ l o g 2 W ] [ 1 ] dp[log_2W][1] dp[log2W][1]

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void write(int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int Size=105;
const int Maxb=35;
int n,W,w[Size],v[Size],sum[Size];
vector<int> a[Maxb];
int dp[Maxb][Size];
int main() {
	while(scanf("%d%d",&n,&W)==2 && n!=-1) {
		memset(dp,0,sizeof(dp));
		memset(sum,0,sizeof(sum));
		for(re i=0; i<=31; i++)	a[i].clear();
		int maxb=0;
		for(re i=1; i<=n; i++) {
			w[i]=read();
			v[i]=read();
			int b=0;
			while(!(w[i]&1)) {
				b++;
				w[i]>>=1;
			}
			a[b].push_back(i);
			if(b>maxb)	maxb=b;
			sum[b]+=w[i];
		}
		for(re i=0; i<=maxb; i++) {
			int len=a[i].size();
			for(re j=0; j<len; j++) {
				for(re k=sum[i]; k>=w[a[i][j]]; k--) {
					dp[i][k]=max(dp[i][k],dp[i][k-w[a[i][j]]]+v[a[i][j]]);
				}
			}
		}
		int logW=log2(W);
		for(re i=1; i<=logW; i++) {
			sum[i]+=(sum[i-1]+1)>>1;
			for(re j=sum[i]; j>=0; j--) {
				for(re k=0; k<=j; k++) {
					dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[i-1][min(sum[i-1],(k<<1)|(W>>(i-1)&1))]);
				}
			}
		}
		printf("%d\n",dp[logW][1]);
	}
	return 0;
}

三、区间dp

3.1 朴素区间dp(略)

3.2 断环为链(略)

3.3 四边形不等式优化

3.3.1 例题 [NOI1995]石子合并

显然是一个区间dp,最小得分方程为 d p [ i ] [ j ] = min ⁡ { d p [ i ] [ k ] + d p [ k + 1 ] [ j ] + s u m ( i , j ) } dp[i][j]=\min\{dp[i][k]+dp[k+1][j]+sum(i,j)\} dp[i][j]=min{dp[i][k]+dp[k+1][j]+sum(i,j)}。满足四边形不等式,断环为链后用四边形不等式优化。
最大得分无法用四边形不等式优化,但是 [ l , r ] [l,r] [l,r]最大得分只能在 k = l k=l k=l k + 1 = r k+1=r k+1=r时取到。

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void write(int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int Size=205;
const int INF=0x3f3f3f3f;
int n,a[Size],sum[Size];
int dpmax[Size][Size],dpmin[Size][Size],s[Size][Size];
int main() {
	memset(dpmin,0x3f,sizeof(dpmin));
	n=read();
	for(re i=1; i<=n; i++)	a[i]=a[i+n]=read();
	for(re i=1; i<=(n<<1); i++) {
		sum[i]=sum[i-1]+a[i];
		dpmax[i][i]=dpmin[i][i]=0;
		s[i][i]=i;
	}
	for(re len=2; len<=n; len++) {
		const int maxl=(n<<1)-len+1;
		for(re l=1; l<=maxl; l++) {
			const int r=l+len-1,val=sum[r]-sum[l-1];
			dpmax[l][r]=max(dpmax[l][r-1],dpmax[l+1][r])+val;
			int minn=INF,id=0;
			for(re k=s[l][r-1]; k<=s[l+1][r]; k++) {
				if(dpmin[l][k]+dpmin[k+1][r]+val<minn) {
					minn=dpmin[l][k]+dpmin[k+1][r]+val;
					id=k;
				}
			}
			dpmin[l][r]=minn;
			s[l][r]=id;
		}
	}
	int ansmax=0,ansmin=INF;
	for(re i=1; i<=n; i++) {
		ansmax=max(ansmax,dpmax[i][i+n-1]);
		ansmin=min(ansmin,dpmin[i][i+n-1]);
	}
	printf("%d\n%d",ansmin,ansmax);
	return 0;
}

四、状压dp

4.1 朴素状压dp

4.1.1 模板题 关灯问题II

水题,记忆化bfs就可以了。
另外吐槽一下,洛咕上面题解都写丑了,每次按按钮不用 O ( n ) O(n) O(n)更新,用小技巧可以做到 O ( 1 ) O(1) O(1)

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#include<vector>
#define re register int
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline void write(int x) {
	if(x>9)	write(x/10);
	putchar(x%10+'0');
}
const int Size=105;
const int Maxn=2055;
const int INF=0x3f3f3f3f;
int n,m,a[Size],b[Size],Queue[Maxn],dis[Maxn];
bool vis[Maxn];
int bfs() {
	int hd=0,tl=0;
	vis[Queue[++tl]=(1<<n)-1]=true;
	while(hd<tl) {
		int x=Queue[++hd];
		for(re i=1; i<=m; i++) {
			int val=x&a[i]|b[i];
			if(!vis[val]) {
				vis[val]=true;
				Queue[++tl]=val;
				dis[val]=dis[x]+1;
				if(!val) {
					return dis[val];
				}
			}
		}
	}
	return -1;
}
int main() {
	n=read();
	m=read();
	for(re i=1; i<=m; i++) {
		for(re j=1; j<=n; j++) {
			int val=read();
			if(val!=1)	a[i]^=1<<(j-1);
			if(val==-1)	b[i]^=1<<(j-1);
		}
	}
	printf("%d",bfs());
	return 0;
}

4.1.2 模板题 [SCOI2007]排列

也是水题……

4.2 插头dp

五、多维dp

5.1 朴素多维dp

5.1.1 模板题 乌龟棋

5.2 多维dp优化

六、树形dp

七、期望dp

7.1 朴素期望dp

7.1.1 模板题 换教室

7.1.2 例题 World of warcraft

7.2 高斯消元优化循环转移

7.2.1 例题 [HNOI2013]游走

八、数位dp

8.1 朴素数位dp

8.1.1 模板题 [SCOI2009]windy数

8.1.2 模板题 [ZJOI2010]数字计数

九、决策单调性优化

9.1 与决策单调性相关的定义及性质(略)

9.2 单调队列/二分查找优化

9.2.1 例题 [NOI2009]诗人小G

9.2.2 例题 数据分块鸡

9.3 斜率优化

9.3.1 [ZJOI2007]仓库建设

9.4 cdq分治套斜率优化

9.4.1 模板题 [SDOI2012]任务安排

9.4.2 模板题 [CEOI2017]Building Bridges

十、数据结构优化

10.1 简单数据结构优化dp(略)

10.2 线段树/树状数组优化dp

10.2.1 模板题 摔跤选手yxc

10.2.2 例题 [SCOI2014]方伯伯的玉米田

十一、动态dp

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值