浅谈状态压缩DP

版权声明:不得未经允许转载,否则后果自负 https://blog.csdn.net/qq_41814502/article/details/85344443

目录

 

什么是状态压缩DP

黑科技操作

集合看作bool数组充当二进制转十进制压缩整型

旅行商问题TSP


什么是状态压缩DP

你是否还在为想不出DP的状态转移方程而烦恼?你现在又可以多一种选择了!那就是状态压缩DP!

但我本人还是建议先将DP的思维提升上去,现在的题我们无非就三种思路!

一.暴搜DFS维持生活骗分大法,与其结合的还有打表法,过样例法和各种玄学卡常!

二.一看就知道却又不知道怎么搞的DP,此法算是可以将生活开的算好了,但是,一但想不出状态转移方程,就只有用第一种了。

三.根本难想的思维法(什么贪心啊,还要用什么数学之类的知识等)

那么,我们的DP现在都是整型的(光是整型就难搞),但是,我跟你说,下标是集合,也可以!

?还有下标是集合的DP?下标还可以是集合?

其实本质就是利用各种黑科技(位运算)将集合变成整型,这种针对下标是集合的DP就是状态压缩DP了!

为什么叫状态压缩DP,因为你把集合压缩成了一个整型啊!(O ^ O)!

黑科技操作

 有了这张神表,什么位运算就不再复杂了!我建议大家在用位运算的时候打上括号,它并不是按从左往右,而是按优先顺序!

https://baike.baidu.com/item/%E8%BF%90%E7%AE%97%E7%AC%A6%E4%BC%98%E5%85%88%E7%BA%A7/4752611?fr=aladdin

来来来,给大家发福利,以后再也不用担心不打括号出错了!

集合看作bool数组充当二进制转十进制压缩整型

旅行商问题TSP

一个商品推销员要去若干个城市推销商品,该推销员从一个城市出发,需要经过所有城市后,回到出发地。应如何选择行进路线,以使总的行程最短?请输出最短行程。节点个数N满足2≤N≤20,路的长度小于1000。

比如这个图,我们怎么走呢?我也不知道怎么走最短,反正就是从1走到其他点,走完后再走一步恰好回到1点,中途每个点只用走一次,比如 1->4->5->2->3->1 就是一条合法路线

这道题的题解很多!

https://baike.baidu.com/item/%E6%97%85%E8%A1%8C%E5%95%86%E9%97%AE%E9%A2%98/7737042?fr=aladdin

我又发福利了!据说这道题,可谓当初难倒众人,但是,一个神奇的动物,蜜蜂,却轻而易举就解读了(不是叫它来打代码)!

2010年10月25日,英国一项最新研究说,在花丛中飞来飞去的小蜜蜂显示出了轻易破解“旅行商问题”的能力,而这是一个吸引全世界数学家研究多年的大问题,如能理解蜜蜂的解决方式,将有助于人们改善交通规划和物流等领域的工作。英国伦敦大学皇家霍洛韦学院等机构研究人员报告说,小蜜蜂显示出了轻而易举破解这个问题的能力。他们利用人工控制的假花进行了实验,结果显示,不管怎样改变花的位置,蜜蜂在稍加探索后,很快就可以找到在不同花朵间飞行的最短路径。这是首次发现能解决这个问题的动物,研究报告即将发表在《美国博物学家》杂志上。

进行研究的奈杰尔·雷恩博士说,蜜蜂每天都要在蜂巢和花朵间飞来飞去,为了采蜜而在不同花朵间飞行是一件很耗精力的事情,因此实际上蜜蜂每天都在解决“旅行商问题”。尽管蜜蜂的大脑只有草籽那么大,也没有电脑的帮助,但它已经进化出了一套很好的解决方案,如果能理解蜜蜂怎样做到这一点,对人类的生产、生活将有很大帮助。

据介绍,“旅行商问题”的应用领域包括:如何规划最合理高效的道路交通,以减少拥堵;如何更好地规划物流,以减少运营成本;在互联网环境中如何更好地设置节点,以更好地让信息流动等。

 这个问题可谓是对以后发展是很好的,那么怎么解决?

首先,我们假设DP[S][v]表示的是目前已经走的顶点集合记做S,当前在v结点上,我们这里S集合不将起点1算进去(最后终点到1还是要算的),走到当前这个状态时,还要走到终点所需要的最短总路程,那我们可以很好的得到一个状态转移方程 

DP[S][v] = min(DP[S][v],DP[S ∪{U}][u] + d(v, u))

你会说,这是个什么鬼啊!∪表示集合,也就是在S这个集合基础上,再把u结点放进S集合中,变成的一个新集合!

d(v, u)表示v结点和u结点之间的权值,很明显,你要保证u不在S结合中,不然,你u都在S集合了,干嘛还要去合并呢?

好,基本的我们说了,那么这个状态转移方程的意思就是,当前你走了S集合这些结点了,而你在v结点上,现在你要从v走到u,

那么走到u后,状态就是S和u的结合,你在u结点上了,再加上之间的权值,是不是很有道理?

那么这个集合怎么表示啊!当时我看书时,差点倒地晕过去!

if ( ! ( i >> k & 1) && j != k)

    f[i][j] = min(f[i][j], f[ 1 << k | i ][k] + d[j][k]);

What?这是什么鬼,经过我几个小时的思考!

还是没有想出来,但是,自从我受到高人指点后,那个才叫豁然开朗!

我们就按照上面那个图来说(不用翻了,就在下面)

为了方便,这个1就记作0,2记作1,......

为什么要从0开始啊!等我慢慢说明!

比如我说了 1->4->5->2->3->1 是一个合法的走法,我们也就按这个例子吧!

假设现在我走到的状态是  1->4->5->2 那么对应的就是DP[S][2]  那么这个S指的就是 4 5 2这个集合(说了起点不算进去)

标题是把集合看作bool数组,什么意思?开一个5个空间的数组   0 0 0 0 0

表示 4 5这个集合的话,bool数组就变成了  0 1 0 1 1

这个能看懂吧,集合有哪些结点,对应的下标变成TRUE,但是这样不好看,因为我们的数字都是右边是地位,左边是高位!

所以就这样变一下!

集合是否有这个结点  1 1 0 1 0

                 下标结点   5 4 3 2 1

那么变成一个2进制 就是11010啦,我们不是那个状态转移方程嘛,DP[S][v] = min(DP[S][v],DP[S ∪{U}][u] + d(v, u))

因为u不是在S中的,那么我们怎么判断u结点在不在S集合中呢?如果u等于2,那么S里面是有的,那我们怎么搞呢?

很简单,只要这个二进制从右往左第u个不是1,那么就说明没有u这个结点,我们只需要将这个二进制右移(u-1)位,那么二进制的最后一位就是表示第u个结点了,我们再判断他是不是偶数,如果是,那么说明末尾是0,否则为1,

也就是!(S>>(u-1)&1)就可以了!

那么我们怎么知道S ∪{U}压缩后的整数是什么啊,如果进了这个!(S>>(u-1)&1)  if语句,那么就说明u不在S集合,我们只是想在S的基础上,把表示第u结点的那个0变成1而已,举个例子,如果这个时候u是3,S是(26)(11010),那么S ∪{U}就是30(11110),我们怎么用位运算完成这种操作?

我们这样吧,先把单独只有u的集合的二进制算出来,那么就应该是 1<<u 对吧! 比如只有3  00100  就是 1<<3 = 8.

那么11010和00100怎么合并?是不是两个 | 一下就行了?  | 表示的是有一个是1就为1

  11010

| 00100

  11110

因为有1就是1,那么原来S有的结点合并后肯定也有,多出来的u结点补充进去,就可以了,所以就是 S | 1<<u  这里如果大家对位运算的优先级别很了解的话,是可以不打括号的,因为<<的级别比 | 高,但是我还是建议不自信的把括号打上!

比如 1<<3-1等于多少呢?是7吗?你错了!是4 !!! 震惊!怎么就是4了!  因为 - 比 << 级别高!

dp[(1 << n)-1][0] = 0;
for(int s = (1 << n)-2; s>=0; s--)
{
    for(int v = 0; v<n; v++)
    {
        for(int u = 0; u<n; u++)
        {
            if(!(s >> u & 1))
            {
                dp[s][v] = min(dp[s][v],dp[s | 1 << u][u] + d[v][u]);
            }
        }
    }
}

 这个是以0开始的,所以S>>(u-1)&1就没有-1,这个聪明的你们肯定就知道啦!

后面的状态压缩DP会更新的,因为我也看的比较少嘛,这只是其中一种方法!!!

猜你喜欢

转载自blog.csdn.net/qq_41814502/article/details/85344443
今日推荐