有关状压DP
【以下内容仅为本人在学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!】
引言
动态规划虽然已经是对暴力算法的优化,但在某些比较特别的情况下,可以通过一些小技巧进一步对其优化,通产我们会在时间与空间中做权衡,在时间可以接受度范围内,适当的以时间为代价换取更小空间的占用;在不爆空间的情况下,适当的以空间换时间。在此,本人将以目前总结的经验详细介绍状态压缩与状压DP。
状态压缩
(一)状态
状态指某个事物表现出来的形态(百度百科)。联系前面的文章(有关动态规划 - PaperHammer - 博客园 (cnblogs.com)),我们在分析解决动态规划的问题时,其重点是在“分情况定变量”这一步,即有多少种选择,如何选择。不妨把这每一个选择视为一个事物,则它表现出来的形态就是所做出的选择,更进一步就是选择的结果。所以,动态规划中的状态实际上就是每一种情况。
(二)压缩
压缩是一种通过特定的算法来减小计算机文件大小的机制,其目的是减少所占空间的同时提高运算速度(百度百科)。压缩的本质是减小,但不可否认虽然减小了文件体积提高了传输速度,但会存在文件质量的下降。
(三)状态压缩
一般地,我们规定利用计算机的二进制性质来描述所做出的选择,即将所有情况统一分为:行或不行、放或不放等两种情况(将很多情况归于两类情况)。由此可以发现,对于状态压缩,我们是针对有多少种选择进行了压缩,即对空间进行优化,那么根据互斥性原则(自己编的名字),对空间上的优化势必会带来时间上的复杂。所以,状态压缩在时间上其实是一种很暴力的思想,它需要遍历每一种情况,每种情况有两种选择(0或1),最高会达到2n的时间复杂度,但针对一些题目,可以依靠某些限定条件,大幅降低时间复杂度,从而变相达到既优化了空间也优化了时间的目的。
【注:相关位运算内容在此不作说明】
举例
(一)吃奶酪
链接:P1433 吃奶酪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题意:房间里放着n块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在(0,0)点处。
分析:据题意可将其抽象为,从原点出发,返回走过所有点的最小距离。最值问题容易想到搜索。搜索时跳过重复路径,最后进行比较即可。
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace ConsoleApp1
7 {
8 internal class Program
9 {
10 static int n;
11 static double res = double.MaxValue;
12 static NodeToNode[][] ntn;
13 static int[] vis;
14 static void Main(string[] args)
15 {
16 Program p = new Program();
17 n = int.Parse(Console.ReadLine());
18 n += 1;
19 double[] x = new double[n], y = new double[n];
20 x[0] = 0;
21 y[0] = 0;
22 for (int i = 1; i < n; i++)
23 {
24 string[] inp = Console.ReadLine().Split(' ');
25 x[i] = double.Parse(inp[0]);
26 y[i] = double.Parse(inp[1]);
27 }//录入所有点,包括(0,0)
28 ntn = new NodeToNode[n][];
29 for (int i = 0; i < n; i++) ntn[i] = new NodeToNode[n];
30 for (int i = 0; i < n; i++)
31 {
32 for (int j = 0; j < n; j++)
33 {
34 ntn[i][j].dis = Dis(x[i], y[i], x[j], y[j]);//计算每两个点间的距离并编号
35 ntn[i][j].next = j;
36 }
37 ntn[i] = ntn[i].OrderBy(k => k.dis).ThenBy(k => k.next).ToArray();//一定要注意这个排序方式!!!!!
38 }
39
40 //for (int i = 0; i < n; i++)
41 // for (int j = 0; j < n; j++)
42 // Console.WriteLine(ntn[i][j].dis + " " + ntn[i][j].next);
43
44 vis = new int[n];
45 vis[0] = 1;
46 p.Dfs(0, 0.0, 1);
47 Console.WriteLine(res.ToString("0.00"));
48 //Console.ReadLine();
49 }
50 public void Dfs(int cur, double sum, int cnt)
51 {
52 if(cnt == n)
53 {
54 res = res <= sum ? res : sum;
55 return;
56 }
57 if (sum >= res) return;
58 for(int i = 0; i < n; i++)
59 {
60 if(vis[ntn[cur][i].next] == 0 && cur != ntn[cur][i].next)
61 {
62 vis[ntn[cur][i].next] = 1;
63 Dfs(ntn[cur][i].next, sum + ntn[cur][i].dis, cnt + 1);
64 vis[ntn[cur][i].next] = 0;
65 }
66 }
67 }
68 public struct NodeToNode
69 {
70 public double dis;
71 public int next;
72 }
73 private static double Dis(double x1, double y1, double x2, double y2)
74 {
75 return Math.Sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
76 }
77 }
78 }
这种思路本质上是对除原点以外的所有点,进行全排列,比较每一种排列结果,其时间复杂度为O(N*N!),(应该是),在比赛中一般仅能承受N<=10的数据范围。
再分析:因为在深度搜索中会出现大量重复访问的数据,所以优化搜索算法,最先想到的一般是记忆化处理,记住到达某一个点的最小距离。但这种方法不能保证最坏的情况,有一部分情况和之前无异。本题除了距离因素,就只剩奶酪因素,既然记住距离还不够,那就只能在“奶酪”上下手了。
对于每个奶酪,只有两种情况:吃过/没吃过;抽象出来即,对于每个点:走过/没走过。发现对于每个事物(奶酪/点),仅有两种状态(访问过/没访问过),满足基本状态压缩的前提,所以我们考虑使用二进制来表示每个状态。
a. 大化小:最终问题是让总距离最小,最终距离是基于每一次选择得来的,那么就把从一个点到一个新的点,这一选择视为一个子问题。并且每次选择总以最小最小距离为基础进行操作,每次选择处理方式相同。
b. 分情况定变量:对于每个点,只有两种情况(走或不走);我们需要知道当前这个点是否走过,所以要记录当前所在点;还需要知道已经走过了几个奶酪,所以要记录对点(奶酪)的访问情况,共两个变量。用f[i][j]表示老鼠当前走到第i个点(奶酪)处,且走过的点的二进制状态为j时,最短的距离。
如:可以使用二进制10100110来表示已经走过第2、3、6、8个奶酪(定义此处索引从1开始),此时j的值为166。需要注意的是,第i个状态是从低位向高位的第i位,即从低位向高位进行转移。
c. 推方程:如果要走这个点(保证该点可访问)那么f[i][j] = f[j][k - (1 << (i − 1)] + dis(i, j)其中f[i][j]表示以i为起点走成状态j的最小距离,dis表示两点间距离。
【解释 k - (1 << i − 1) 】
(1) 符号‘<<’:表示某十进制数对应的二进制数向右移,等价于对十进制数*2。
如:1 << 3表示将1对应的二进制编码向右移动4位 (1)10 = (0001)2 右移5位变为(1000)2 = (8)10 = 1 * 23
(2)式子:最终目标的状态 – 当前位置的状态 = 中间状态
如:(5)10 =(0101)2 = (0111)10 – (0010)2 = (7)10 - (2)10
上一个中间状态 = 目标状态 – 当前位置状态
【难点】为什么可以用 k & (1 << (i – 1)判断合法位置
我们设一个状态 k = 01101,表示第一、三、四列(从低位开始)中的某个点已经访问过;
由于我们是一行一行访问的,所以在k状态我们应该访问第三行的点了,那第三行我们应该访问哪个点,或者说状态k由哪些状态转移而来呢?
状态k(01101)由三种状态(必然是前两行的状态)来的:
前两行在三、四列已访问,第三行只好访问第一列;(01100)
前两行在一、四列已访问,第三行只好访问第三列;(01001)
前两行在一、三列已访问,第三行只好访问第四列;(00101)
无非就是这三种情况,现在我们来考虑怎么来表示状态s由这三种状态来的,k & (1 << (i - 1))就是用来实现这个功能的,即判断当前情况是否为其上一个情况转移而来。
for (int i = 1; i <= 4; i++) 01101 & (1<<0) = 01101 & 1 = 00001 01101 & (1<<1) = 01101 & 10 = 00000 01101 & (1<<2) = 01101 & 100 = 00100 01101 & (1<<3) = 01101 & 1000 = 01000 01101 & (1<<4) = 01101 & 10000 = 00000
由此得出,只有和k=01101有1重合结果才大于0,根据这个特性判断此列是否可以访问。
d. 定边界:本题在搜索过程中不存在索引非法而导致的无法访问点,但需要对初值进行设定,因为其默认值为0,我们需要存储最小距离,所以应对初值设定为一个较大的值。
【注:文末代码中已附有更加详细的注释】
(二)01背包
【注:题目解析请转至该文章有关动态规划 - PaperHammer - 博客园 (cnblogs.com)】
在该问题中,对于每种物品只有两种选择,放与不放,故也可以使用状态压缩进行优化。
如:有5件物品,
如果这5件物品都不放的话,那就是00000;
如果这5件物品都放的话,那就是11111;
观察可知,在上面的例子中00000 ~ 11111可以代表所有的情况,转化为十进制就是0到(1 << (5 – 1));
其中,所以f[10000]只能从f[00000] + W[1] 转移过来;f[11000]可以从f[01000] + W[1]或者f[10000] + W[2]转移过来,以此类推
总结
1. 注意位运算的运算等级顺序,括号很重要
2. 状压dp的特点一般是规模比较小,一般小于15,最多不超过20;而且一般只有两种决策
状态压缩比较难理解,本篇文章也花了快两周时间,虽然写成了笔记,但本人理解程度依旧不深,在此希望与各位大佬相互交流一起学习
【感谢您可以抽出时间阅读到这里;受限于水平,内容可能会有许多不妥之处,许多地方可能存在错误,还请各位大佬留言指正,请见谅,谢谢!】
#附文中所提到的第一题的代码 及 状压模板
(1)吃奶酪
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace ConsoleApp1
7 {
8 internal class Program
9 {
10 static int n;
11 static double res = double.MaxValue;
12 static double[][] f;
13 static double[] x, y;
14 static void Main(string[] arg)
15 {
16 n = int.Parse(Console.ReadLine());
17 x = new double[20];
18 y = new double[20];
19
20 for (int i = 1; i <= n; i++)
21 {
22 string[] inp = Console.ReadLine().Split(' ');
23 x[i] = double.Parse(inp[0]);
24 y[i] = double.Parse(inp[1]);
25 }
26
27 //初始化,因为之后的比较是取较小的一个,所以将所有值预设为最大
28 f = new double[20][];
29 for(int i = 1; i <= n; i++)
30 {
31 f[i] = new double[35000];
32 Array.Fill(f[i], double.MaxValue);
33 }
34
35 DP(f, n);
36 Console.WriteLine(res.ToString("0.00"));
37 //Console.ReadLine();
38 }
39 static public double DP(double[][] f, int n)
40 {
41 for (int k = 1; k <= (1 << n) - 1; k++)
42 for (int i = 1; i <= n; i++)
43 {
44 if ((k & (1 << (i - 1))) == 0) continue;//已经访问过
45 if (k == (1 << (i - 1)))
46 {
47 f[i][k] = 0;//标记为不可访问
48 continue;//当前位置与目标位置相同
49 }
50 for(int j = 1; j <= n; j++)
51 {
52 if ((k & (1 << (j - 1))) == 0 || i == j) continue;//如果不可访问 或 两个位置相同 则跳过
53 f[i][k] = Math.Min(f[i][k], f[j][k - (1 << (i - 1))] + Dis(i, j));
54 }
55 }
56 for (int i = 1; i <= n; i++)
57 {
58 double cur = f[i][(1 << n) - 1] + Dis(i, 0);
59 res = Math.Min(res, cur);
60 }
61
62 return res;
63 }
64 private static double Dis(int a,int b)
65 {
66 return Math.Sqrt((x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]));
67 }
68 }
69 }
(2)一般状压模板
1 int n;
2 int maxn = 1 << n;//总状态数。
3 //枚举已有的集合数。按照状态转移的顺序,一般从小编号到大编号。
4 for(int i = 1; i <= m; ++ i){
5 //枚举当前集合中的状态。
6 for(int j = 0; j < maxn; ++ j){
7 //判断当前集合是否处于合法状态,通常我们需用一个数组提前处理好。如g数组;
8 if(当前状态是否合格){
9 for(int k = 0; k < maxn; ++ k){
10 //枚举上一个集合的状态。
11 if(上一个集合的状态是否合格 + 上一个集合的状态和当前状态的集合是否产生了冲突){
12 列写状态转移方程。
13 }
14 }
15 }
16 }
17 }
18 }
有关状压DP的更多相关文章
- BZOJ 1087: [SCOI2005]互不侵犯King [状压DP]
1087: [SCOI2005]互不侵犯King Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 3336 Solved: 1936[Submit][ ...
- nefu1109 游戏争霸赛(状压dp)
题目链接:http://acm.nefu.edu.cn/JudgeOnline/problemShow.php?problem_id=1109 //我们校赛的一个题,状压dp,还在的人用1表示,被淘汰 ...
- poj3311 TSP经典状压dp(Traveling Saleman Problem)
题目链接:http://poj.org/problem?id=3311 题意:一个人到一些地方送披萨,要求找到一条路径能够遍历每一个城市后返回出发点,并且路径距离最短.最后输出最短距离即可.注意:每一 ...
- [NOIP2016]愤怒的小鸟 D2 T3 状压DP
[NOIP2016]愤怒的小鸟 D2 T3 Description Kiana最近沉迷于一款神奇的游戏无法自拔. 简单来说,这款游戏是在一个平面上进行的. 有一架弹弓位于(0,0)处,每次Kiana可 ...
- 【BZOJ2073】[POI2004]PRZ 状压DP
[BZOJ2073][POI2004]PRZ Description 一只队伍在爬山时碰到了雪崩,他们在逃跑时遇到了一座桥,他们要尽快的过桥. 桥已经很旧了, 所以它不能承受太重的东西. 任何时候队伍 ...
- bzoj3380: [Usaco2004 Open]Cave Cows 1 洞穴里的牛之一(spfa+状压DP)
数据最多14个有宝藏的地方,所以可以想到用状压dp 可以先预处理出每个i到j的路径中最小权值的最大值dis[i][j] 本来想用Floyd写,无奈太弱调不出来..后来改用spfa 然后进行dp,这基本 ...
- HDU 1074 Doing Homework (状压dp)
题意:给你N(<=15)个作业,每个作业有最晚提交时间与需要做的时间,每次只能做一个作业,每个作业超出最晚提交时间一天扣一分 求出扣的最小分数,并输出做作业的顺序.如果有多个最小分数一样的话,则 ...
- 【BZOJ1688】[Usaco2005 Open]Disease Manangement 疾病管理 状压DP
[BZOJ1688][Usaco2005 Open]Disease Manangement 疾病管理 Description Alas! A set of D (1 <= D <= 15) ...
- 【BZOJ1725】[Usaco2006 Nov]Corn Fields牧场的安排 状压DP
[BZOJ1725][Usaco2006 Nov]Corn Fields牧场的安排 Description Farmer John新买了一块长方形的牧场,这块牧场被划分成M列N行(1<=M< ...
- 【BZOJ1087】 [SCOI2005]互不侵犯King 状压DP
经典状压DP. f[i][j][k]=sum(f[i-1][j-cnt[k]][k]); cnt[i]放置情况为i时的国王数量 前I行放置情况为k时国王数量为J #include <iostre ...
随机推荐
- 列举 IoC 的一些好处?
IoC 的一些好处是:它将最小化应用程序中的代码量.它将使您的应用程序易于测试,因为它不需要单元测试用例中的任何单例 或 JNDI 查找机制.它以最小的影响和最少的侵入机制促进松耦合.它支持即时的实例 ...
- Saltstack自动化扩容
一. etcd服务的安装和使用 1.安装etcd应用: wget https://github.com/coreos/etcd/releases/download/v2.2.5/etcd-v2.2.5 ...
- resin服务之三---独立resin的配置
独立resin的配置 关掉httpd服务: [root@data-1-1 ~]# killall httpd [root@data-1-1 ~]# lsof -i :80 ------>h ...
- Python - set类型
- 攻防世界 NaNNaNNaNNaN-Batman
NaNNaNNaNNaN-Batman 下载出一个文件我们一开始不知道是个啥,我们拉入到sublime中看一下 我们可以发现在最开始的位置有一个_是一段函数变量,最后的eva()那个是执行函数代码,但 ...
- 在VisualStudio调试器中使用内存窗口和查看内存分布
调试模式下内存窗口的使用 在调试期间,"内存"窗口显示应用使用的内存空间.调试器窗口(如"监视"."自动"."局部变量" ...
- 细说Web API中的Blob
在一般的Web开发中,很少会用到Blob,但Blob可以满足一些场景下的特殊需求.Blob,Binary Large Object的缩写,代表二进制类型的大对象.Blob的概念在一些数据库中有使用到, ...
- pip——重新安装
原因 没有用管理员的权限安装.导致失败. 并且pip还被卸载了. 使用环境 win10 重新安装pip python -m ensurepip 更新pip 管理员打开cmd python -m pip ...
- 异步任务-springboot
异步任务-springboot 异步:异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作.也就是说无论异步方法执行代码需要多长时间,跟主线程没有任何影响,主线程 ...
- Model, HttpServletRequest, ModelMap区别
看了spring mvc的底层会发现,model数据最终还是写到HttpServletRequest属性中,只是model的写法更体现了MVC思想减少各层间耦合 写法: 1.request.setAt ...