zhxのDP讲

  • zhxのDP讲

DP基础例题

1. 斐波那契数列

f[1]=1;//第0项和第1项
f[0]=0;

//记忆化搜索
int dp(int n)//斐波那契数列第n项
{
//g[i]表示f[i]有没有计算过
if(n<=1)return n;
if(g[n])return f[n];
g[n]=true;
f[n]=dp(n-1)+dp(n-2);
return f[n];//O(fn)
}


//其它求当前
for(int i=2;i<=a;i++)
	f[i]=f[i-1]+f[i-2];
//O(n)


//当前求其它
for(int i=0;i<=n;i++)
{
//O(n)
f[i+1]+=f[i];
f[i+2]+=f[i];
}

2. 组合数

for (int i=0;i<=n;i++)
{
	C[i][0] = 1;//处理边界
	for (int j=1;j<=i;j++)
		C[i][j] = C[i-1][j-1] + C[i-1][j];
}
  1. IOI数字三角形及其系列

状态:f[位][置]=经过的数字之和

f[i][j]:走到 (i,j) 的数字最大和

  1. 滑雪

//滑雪
n行m列
可以走任意四个方向
每走一步需要使得脚底数字变大
最多走几个格子

f[x][y]代表走到(x,y)的最长长度
若:a[x-1][y]>a[x][y];
f[x-1][y]=max(f[x-1][y],f[x][y]+1)
//不正确解,使用自己求其他
//但没有保证当前的位置求出
cin  >> n >> m;
for (int i=1;i<=n;i++)
	for (int j=1;j<=m;j++)
		cin >> a[i][j];

for (int i=1;i<=n;i++)
	for (int j=1;j<=m;j++)
		f[i][j]=1;

for (int x=1;x<=n;x++)
	for (int y=1;y<=m;y++)
		for (int d=上下左右)
		{
			int xx = x朝着d走;
			int yy = y朝着d走;

			if ( (xx,yy)存在 && a[xx][yy] > a[x][y]) f[xx][yy] = max(f[xx][yy],f[x][y]+1); 
		}
  1. 最长上升子序列

个人笔记
https://www.cnblogs.com/frankchenfu/p/7107019.html

  1. 乌龟棋
DP三要素:
  1. 状态
  2. 转移方程
  3. 边界条件

DP可加维度

f[i][j](二维)—f[i][j][k](三维)—f[i][j][k][l](四维)

若时间空间过高,削减冗余状态,减少维度
建一个和原来状态相同的数组储存方案或方案数

有n*m的方格图

DP模型

背包DP

01背包


n个物品 背包容积为m 

第i个物品体积为vi,价值为wi

需要向背包里装一定的物品,在体积不超限的情况下装价值最高的物品

f[编号][体积]=价值

f[i][j]表示考虑完前i个物品 背包已经使用了j的体积

//01背包
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
//v体积 w价值

for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
	{
		//考虑求f[i][j]
		f[i][j]=f[i-1][j];//第i个物品不选
		if(j>=v[i])		
			f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i])
	}

例题采药

无穷背包

n种物品 背包容积为m
每种物品数量不限
第i个物品体积为vi,价值为wi
f[i][j]表示考虑完前i种类物品 背包已经使用了j的体积

//无穷背包

cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
//v体积 w价值

for(int i=1;i<=n;i++)
	for(int j=0;j<=m;j++)
	{
		//考虑求f[i][j]
		f[i][j]=f[i-1][j];//第i个物品不选
		if(j>=v[i])		
			f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i])
	}
例题疯狂的采药

有限背包(多重背包)

有N种物品和一个容量为T的背包,第i种物品最多有M[i]件可用,价值为P[i],体积为V[i]

求解:选哪些物品放入背包,可以使得这些物品的价值最大,并且体积总和不超过背包容量。

//有限(多重)背包
cin>>n>>m;

int k=0;
//v体积 w价值 u个数 
for(int i=1;i<=n;i++)
{
int v_,w_,x_;
cin>>v_>>w_>>x_;
int r=1;
while(x_>=r)
{
	k++;
	v[k]=v_*r;
	w[k]=w_*r;
	x_-=r;
	r*=2;
}
if(x_!=0){
k++;
v[k]=v_*x_;
w[k]=w_*x_;

}

}

for(int i=1;i<=n;i++) 
	for(int j=0;j<=m;j++) 
		for(int k=0;k*v[i]<=j&&k<=u[i];k++){
			//考虑如何求f[i][j] 
    f[i][j]=max(f[i][j],f[i-1][j-kv[i]]+k*w[i]);
}

区间DP

区间DP特点:

  1. 状态设计前两维度一定为f[L][R];
  2. n<=100/200/500
  3. 只能对相邻的两个元素合并为一个
  4. 若区间为环形,将原数组在后面复制,组成长度为2n的数组,再进行区间DP
  5. 枚举分界点

1. 石子合并

f[l][r]表示区间[l,r]合并一堆的最小代价

//石子合并
//????[????][????]表示一段区间合并的最小代价
//枚举断点合并
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}

for(int i=1;i<=n;i++)
{

sum[i]=sum[i-1]+a[i];
f[i][i]=0;
}
for(int len=2;len<=n;len++)
	for(int l=1,r=len;r<=n;l++,r++)
			for(int k=l;k<r;k++)
			f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);

2.能量项链

环形区间DP

3.括号序列

给出一个的只有()[]四种括号组成的字符串,
求最多能够选出多少个括号满足完全匹配

  • 状态:为f[l][r] 代表区间[l,r]最多取出多少括号
  • 边界:f[i][i]=0;
  • 转移方程:

4.回文子串

求序列内有多少回文子串

两个回文子串只要位置不同就算做不同的回文子串

s[l]=s[r]

s[l+1]=s[r-1]

  • 状态:f[l][r]表示l~r有多少回文子串

求回文子序列

  • 状态:f[l][r]表示l~r有多少回文子序列

关键问题在于去重

转移一:容斥原理

转移二:当左右字符相同时 再补充一次内部答案

树形DP

前置知识

基于树的dp

  1. Dp方法始终为从下至上进行dp
  2. 在每个节点对所有儿子做聚合
  3. 可能需要多一遍dfs或者bfs
  • 状态:[i]表示以i为根的子树有多少个点
  • 边界条件以叶子节点来设置:f[叶子节点]=1;
  • 转移方程:将所有儿子的信息合并得到根节点信息f[i]=f[p1]+f[p2]+····+f[pk]+i;
//树形DP
void dfs(int i){
	if(i is yezi)
	{
		f[i]=1;
		return ;
	}
	for(p 是 i的 儿子)
	{
		dfs(p);
		f[i]+=f[p];
	}

	f[i]++;

}

Problem 1

询问树的最大独立集

n个点的树,
最多选几个点使得其互不相邻

  • 状态:f[i][j]

i表示以i为根的子树最多选几个点

j表示当前的i点选或不选(0/1)

f[叶子][j]

类似例题 没有上司的舞会

Problem 2

现在要在一棵树上布置士兵,每个sb在结点上,每个sb可以守护其结点直接相连的全部边,问最少需要布置多少个sb

  • 状态:f[i][j]以i为根的子树最少几个伞兵
    i表示以i为根的子树

j取值为0/1/2

  • 0:i点放了伞兵

  • 1:i没放但是儿子放了

  • 2:i没有放并且儿子都没有放

f[叶子][1]=无穷;
f[叶子][2]=0;

Problem 3

求树上最远的两个点的距离(求树的直径)

f[i][0]:i向下走最长的路

f[i][1]:i向下走次长的路

f[叶子][0/1]=0;

f[i][0]=1+max(f[p][0],f[i][0]);

Problem 4

树形01背包(依赖背包)

n个物品,背包容积为m,第i个物品体积为vi价值为wi,n个物品构成树,i进入背包前需要保证父亲已经进入背包

f[i][j]以i为根的子树,已经用了j的体积,能获得的最大价值之和

金明的预算方案

排列DP

前置

对于某个n!种排列中满足某个条件的排列有多少种
需要把1~n这些数按照顺序(小到大/大到小)顺序插入进去

Problem 1

1~n排列中有多少个排列的逆序对的数量是偶数

方程:

f[i][j]插入i个数逆序对数为j

f[i+1][j+i-k]+=f[i][j];

但是!
此题可以不用DP

ans为\(\frac{n!}{2}\)

Problem 2

对于一个序列,定义其“激动值”为序列中严格大于前面所有数的
元素的个 数。比如,{1,1,5,6,5}的激动值为3。 给定n个数
p1,p2,...,pn,求这n个数的所有排列中,激动值不超过k的个数。
1 ≤ k ≤ n ≤ 200,1 ≤ pi ≤ 200
从大到小插入
f[i][j]表示插入到i激动值为j的方案数
f[i-1][j]+=f[i][j]*x;

Problem 3

1~n的排列中,有多少最长上升子序列长度≤2

Problem 4

N个字符串,由数字组成,有n!种方法把它们连在一起,能得到n!个数,n!个数中有多少是11的倍。
∵奇位和-偶位和=11的倍数
∴这个数为11的倍数

数位DP

posted @ 2021-07-17 19:36  DAIANZE  阅读(101)  评论(0编辑  收藏  举报