背包问题
看另一篇博客,在那里我写的很详细
区间dp
状态表示一般是一维度,但是还要用一维来枚举length,所以一共二维
//AcWing 282. 石子合并
#include <iostream>
#include <cstring>
using namespace std;
const int N = 307;
int a[N], s[N];
int f[N][N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i ++)
{
cin >> a[i];
s[i] += s[i - 1] + a[i];//前缀和
}
memset(f, 0x3f, sizeof f);
// 区间 DP 枚举套路:长度+左端点
for (int len = 1; len <= n; len ++) //从1,2,3一步步枚举,表示一堆石子,两堆石子,三堆石子,
{ // len表示[i, j]的元素个数 //每一个length都是最优的,当然到了length = n的时候,合并成n堆石子的这个区间 也是最优的
for (int i = 1; i + len - 1 <= n; i ++)
{
int j = i + len - 1; // 自动得到右端点
if (len == 1)
{
f[i][j] = 0; // 边界初始化
continue;
}
for (int k = i; k <= j - 1; k ++)
{ // 必须满足k + 1 <= j
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
}
}
}
cout << f[1][n] << endl;
return 0;
}
计数类dp
数位统计dp
现在还不会,后面会了补上
状态压缩dp
将状态用二进制表示
//AcWing 291. 蒙德里安的梦想
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 12, M = 1 << N;//N行最多有2^12个状态,每一行都有两种选择,要么为1 要么为0
int n, m;
LL f[N][M];
vector <int> state[M];
bool st[M];
int main()
{
while (cin >> n >> m, n || m)//只要正在输入n和m,并且m和n不都为0
{
//预处理
for (int i = 0; i < 1 << n; i ++ )//枚举每一列的所有状态
{
int cnt = 0;//计算遇到1之前这一列上面有多少空格,也就是有多少0
bool is_valid = true;
for (int j = 0; j < n; j ++ )//枚举每一行
if (i >> j & 1)//如果当前状态的i的第j位为1,进入状态合法判断1
{
if (cnt & 1)//如果cnt为奇数,那么竖着肯定放不下若干个连续的1*2的长方形
{
is_valid = false;
break;//这种状态不合法,直接遍历下一个状态
}
cnt = 0;//状态合法,cnt置0
}
else cnt ++ ;//如果当前状态的第i位不位1,继续统计遇到1之前有多少个连续的0
if (cnt & 1) is_valid = false;//已经遍历完n行了,验证第n行包括第n行之前的cnt是否为偶数
st[i] = is_valid;//记录一下该状态合法
}
//预处理每种状态之间是否冲突
for (int i = 0; i < 1 << n; i ++ )//遍历一列每一种状态
{
state[i].clear();//防止上一个n,m留下来的数据对本次的n,m造成影响
for (int j = 0; j < 1 << n; j ++ )//遍历一列的每一种状态
if ((i & j) == 0 && st[i | j])//判断两种状态是否冲突,i&j表示两种状态是不通的状态,i|j表示两种状态合并以后是一种合法的状态
state[i].push_back(j);//对于状态i来说,j是一种可以和它共存的状态,预处理存好,到时候就不用一个个遍历每种状态了
}
memset(f, 0, sizeof f);
f[0][0] = 1;
for (int i = 1; i <= m; i ++ )//dp
{
for (int j = 0; j < 1 << n; j ++ )
{
for (auto k : state[j])//取出在j状态下合法的一些状态,之前预处理过
f[i][j] += f[i - 1][k];//因为一个是1*2的长方形,所以i-1和i列正好满足两个
}
}
cout << f[m][0] << endl;
}
return 0;
}
树形dp
dp状态像一棵树一样,不一定是二叉树,每个状态收集结果的时候从它的子节点收集(子节点数不一定<=2)。
记忆化搜索
其实dp本质就是一种暴搜