算法笔记2:状压 dp
优化方式
状压 dp 能将排列复杂度转为指数级复杂度。
题目描述
房间里放着 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 点处。
输入格式
第一行有一个整数,表示奶酪的数量 。
第 2 到第 行,每行两个实数,第 行的实数分别表示第 块奶酪的横纵坐标 ,。
输出格式
输出一行一个实数,表示要跑的最少距离,保留 2 位小数。
暴力求解的话就需要把所有的路径排列:、枚举出来,并计算总长度,复杂度为 ,且不同顺序的路径之间不能够共享子问题的答案,难以优化。
所以状压 dp 的核心思路就是:
- 状态压缩,关心已经走过了的点的集合(用二进制数表示),而不是走过点的顺序,避免枚举全排列
- 动态规划,找到子问题的最优解,并把它们合起来
那么它需要维护的数据就是:
- 当前的状态,也就是走过了的点的集合,毕竟要用动态规划求解,路径是不重要的,只需要找出当前状态的最短的路径和就行
- 当前走到了的点,因为要枚举,所以就从这里入手
这样的话,时间复杂度就变成了 , 代表着要遍历某状态下,当前所处的点的所有的可能, 就是对所有状态的枚举。一般 的数据是可以通过的。
为什么相比于枚举所有路径,状压 dp 能优化呢?因为动态规划让我们根据不同路径走到了相同的状态时,不再需要去寻找别的解,直接复用已经找到了的最优解。
1 |
|
关于
memset
的踩坑使用
memset
对数组进行赋值是按照字节来的,所以有一些需要注意的地方:常见的
0x3f3f3f
=1061109567
,赋值给普通的int
变量,就相当于1e9
没什么问题,但是通过memset
赋值给整型数组的话就不一样了,它是只取最后两个3f
给int
的每一个字节(一个int
占 4 个字节)赋值,所以这个时候0x3f3f3f
和0x3f
是等效的。那么就可以发现:用
memset
赋值的话也可以直接用127
=0111 1111
赋极大值,用128
=1000 0000
赋极小值。对于浮点数,因为引入了阶码所以要另外讨论,如果对
double
数组用memset
赋0x3f3f3f
,只能得到一个很小的值0.000476792
,只有赋127
或者128
才能给它赋成极大值或极小值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int N=1e3;
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int a[N],b[N],c[N];
double d[N],e[N],f[N];
memset(a,127,sizeof(a));
cout<<a[0]<<endl;
memset(b,128,sizeof(b));
cout<<b[0]<<endl;
memset(c,0x3f,sizeof(b));
cout<<c[0]<<endl;
memset(d,127,sizeof(d));
cout<<d[0]<<endl;
memset(e,128,sizeof(e));
cout<<e[0]<<endl;
memset(f,0x3f,sizeof(f));
cout<<f[0]<<endl;
}输出为:
1
2
3
4
5
62139062143
-2139062144
1061109567
1.38242e+306
-2.93745e-306
0.000476792
集合的状压
题目背景
一只队伍在爬山时碰到了雪崩,他们在逃跑时遇到了一座桥,他们要尽快的过桥。
题目描述
桥已经很旧了, 所以它不能承受太重的东西。任何时候队伍在桥上的人都不能超过一定的限制。 所以这只队伍过桥时只能分批过,当一组全部过去时,下一组才能接着过。队伍里每个人过桥都需要特定的时间,当一批队员过桥时时间应该算走得最慢的那一个,每个人也有特定的重量,我们想知道如何分批过桥能使总时间最少。
输入格式
第一行两个数: 表示桥能承受的最大重量和 表示队员总数。
接下来 行:每行两个数: 表示该队员过桥所需时间和 表示该队员的重量。
输出格式
输出一个数表示最少的过桥时间。
需要模拟的对象从一个单独的地点,变成了若干个人组成的集合,怎么样枚举呢?
如果用二进制数 表示状态, 表示已经过去的人,那首先就是从 0 开始顺序枚举每一种情况 ,再枚举它的每一个真子集 ,因为 里面 的数量更少,就表示 是从 转变过来的,那它们进行异或就表示这个状态具体的变化 。写出状态转移:
1 |
|