首页 > 编程语言 >[算法学习笔记][刷题笔记] 2023/8/26&8/27 解题报告状压 dp

[算法学习笔记][刷题笔记] 2023/8/26&8/27 解题报告状压 dp

时间:2023-08-27 11:55:30浏览次数:38  
标签:状态 26 27 int 状压 笔记 枚举 include dp

题单

状压 dp

状压 dp是一种非常暴力的算法,它直接记录不同的状态,通过状态进行转移。

状压 dp可以解决 NP 类问题。它的原理是暴力枚举每一种可能的状态。所以它的复杂度是指数级的。只能求解小范围的问题。

关于记录状态:

状压 dp 通过一个二进制串来记录状态。显然二进制串可以转换成十进制在数组中作为下标。这也是它优化状态的关键。同时,二进制串可以直接通过 位运算 转移状态。

一般来讲思考状压 dp 的步骤是先判断有无后效性,即可否用朴素的 dp 例如线性 dp 解决。如果有后效性,考虑记录完整状态,也就是状压 dp。

接下来将通过一些例题来进一步理解状压 dp。

[USACO12 MAR Cows in a Skyscraper G]

给出 \(n\) 个物品,体积为 \(w_i\),现把其分成若干组,要求每组总体积\(\leq W\),问最小分组。\(n\leq 18\)

Solution

类似于 01 背包,每个物品只能分到一个组中,即后面的状态不能包含前面的状态。有后效性。无法使用朴素线性 dp 解决。

数据范围 \(n\leq 18\)。可以用指数级的算法。

定义 \(f_{i,j}\) 表示分成了 \(i\) 组,且状态为 \(j\) 时的最小体积。

关于状态:我们把状态设计为二进制串,例如 \(00110\) 表示只有第 \(3,4\) 个物品被选择过,被包含在这一个状态内。

定义状态上界:每个物品都可以是 \(0,1\)。因此最多有 \(2^n\) 个状态。我们可以把每一个状态都枚举。因为此类问题有后效性,无法简化。

接下来枚举分成了多少组,显然最多分成 \(n\) 组。

对于每个分组数,我们都可能遇到每个可能状态,所以暴力枚举。

现在,对于分成当前组数,当前状态。我们需要对每个物品进行决策,也就是更新,所以再枚举每个物品。

对于每个物品,如果它被包含在该状态,或者当前分组,当前状态下最后一个分组的体积+当前物品体积超过上限。都不可以在当前状态下选择该物品。

反之,更新状态。若设当前分成了 \(i\) 组,状态为 \(x\),当前是第 \(j\) 个物品,则更新:

\(f_{i,x|1<<j}=min(f_{i,x|1<<j,f_{i,x}+a_j})\)

对于必须将该物品单独分为一组,则:

\(f_{i+1,x|1<<j}=min(f_{i+1,x|1<<j},a_j)\)

Explanation:1<<j 表示新建一个二进制串,并且将 \(1\) 移动到第 \(k\) 位,也就是 \(2^k\) 。\(x|1<<j\) 也就使 \(x\) 状态的第 \(j\) 位变成 \(1\) 后的状态决策。

最后统计答案正着搜到第一个不是 INF 的组输出即可。

上述决策需要注意,必须在 \(f_{i,x}\) 被决策后才能继续。

初始化使得分成1组,状态为每个物品的体积都为它本身,具体实现见代码:

实现
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 1000010;
const int INF = 0x3f3f3f3f;
int up;
int n,w;
int a[N];
int f[20][N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>w;
    for(int i=0;i<n;i++) cin>>a[i];
    up = pow(2,n);
    memset(f,INF,sizeof f);
    for(int i=0;i<n;i++) f[1][1<<i] = a[i];
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<up;j++)
        {
            if(f[i][j] != INF)
            {
                for(int k=0;k<n;k++)
                {
                    if(j&(1<<k)) continue;
                    if(f[i][j]+a[k] <= w)
                    {
                        f[i][j|(1<<k)] = min(f[i][j|(1<<k)],f[i][j]+a[k]);
                    }
                    else
                    {
                        f[i+1][j|(1<<k)] = min(f[i][j|(1<<k)],a[k]);
                    }
                }
            }
        }
    }
    for(int i=0;i<=n;i++)
    {
        if(f[i][up-1] != INF)
        {
            cout<<i<<endl;
            return 0;
        }
    }
}

Luogu P1433 吃奶酪

房间里放着 \(n\) 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 \((0,0)\) 点处。

Solution

我们发现它的位置是必要的。我们只需要记录它最后一步是在哪里即可。

定义 \(f_{i,j}\) 表示状态为 \(i\) 的前提下,最后吃的是 \(j\) 最小需要跑多少。

首先 \(n^2\) 预处理任意两点的距离。

考虑枚举每一种状态,枚举最后吃的哪一个,显然如果枚举到最后吃的这个奶酪不包含在状态中则不处理。

否则,更新它。枚举它可以从哪个节点 \(k\) 转移过来。转移的前提同理是 \(k\) 被包含在这个状态。然后枚举先吃 \(k\),然后再吃当前节点即可。

我们显然不希望重复吃,也就是不重复吃 \(j\),因为我们当前更新的是最后吃 \(j\) 的情况下,具体地:

\(f_{i,j}=min(f_{i,j},f{i^(1<<(j-1)),k}+dist_{k,j})\)

实现
/*
状压dp
一般来说 f 数组中用二进制串来表示状态。一般解决“看似有后效性” 的问题。例如本题我们需要记录一个奶酪究竟有没有被吃。

记录完状态后考虑如何转移。


*/

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int N =18 ;
int n;
double dist[N][N];
double x[N],y[N];
double f[1<<N][N];
int main()
{
    // ios::sync_with_stdio(false);
    // cin.tie(0);
    // cout.tie(0);
    for(int i=1;i<(1<<18);i++)
    {
        for(int j=0;j<18;j++){
            f[i][j]=10000000.0;
        }
    }
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf",&x[i],&y[i]);
        f[1<<(i-1)][i] = (double)sqrt(x[i]*x[i]+y[i]*y[i]);
       // cout<<x[i]<<" "<<y[i]<<endl;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            dist[i][j] = (double)sqrt((x[j]-x[i])*(x[j]-x[i])+(y[j]-y[i])*(y[j]-y[i]));
        }
    }
    for(int i=1;i<(1<<n);i++) //最多有 2^n 种状态。
    {
        for(int j=1;j<=n;j++)  //对于每一种状态进行枚举,以当前走到该节点的前提是本次状态和该节点一致
        {
            if(i&(1<<(j-1)) == 0) continue; //当前节点被包含在该状态时才可以继续
            for(int k=1;k<=n;k++)
            {
                if(j == k) continue; //对于每一个节点考虑先吃 k在吃j 
                if((i&(1<<(k-1))) == 0) continue; //同理如果状态不包含则不能处理
                f[i][j] = min(f[i][j],f[i^(1<<(j-1))][k]+dist[j][k]); //转移,我们不希望重复吃,所以先前的状态应与当前状态相反。
              //  cout<<f[i][j]<<endl;
            }
        }
    }
    double minn = 100000000.0;
    for(int i=1;i<=n;i++) minn = min(minn,f[(1<<n)-1][i]); //状态确定,枚举以哪个节点为结尾。
    printf("%.2lf\n",minn);
  //  cout<<minn<<endl;
    return 0;
}

标签:状态,26,27,int,状压,笔记,枚举,include,dp
From: https://www.cnblogs.com/SXqwq/p/17660102.html

相关文章

  • 设计模式学习笔记——接口隔离原则
    定义:1、客户端不应该依赖于它不需要的接口2、类间的依赖关系应该建立在最小的接口上通俗的讲,应该建立单一的接口,不要建立臃肿庞大的接口,即接口应该尽量细化,同时接口中的方法尽量少。举例:要成为一名美女必须具备三个条件:面貌、身材、气质,星探找美女的过程如下类图所示:IPrettyGirl接......
  • 设计模式学习笔记——创建者模式
    这个模式也是比较难理解的,我看了《设计模式之禅》上讲解的例子,但是看完之后一头雾水,而且好乱,仍然没有理解,看了好几遍,还是没有理解,于是我又去翻开我的课本,看那上面的例子,但是结果依然。于是上网搜,搜了很多,但是都不是很理想,最终功夫不负有心人,终于找到一个我能理解,而且我认为比较合理......
  • oracle学习笔记(9)——逻辑存储结构——区
    1、区的概念:   区是由一系列连续的数据块构成的逻辑存储单元,是存储空间分配与回收的最小单位。当创建一个数据库对象时,Oracle为对象分配若干个区,以构成一个段来为对象提供初始的存储空间。当段中已分配的区都写满后,Oracle会为段分配一个新区,以容纳更多的数据。2、区的管理(1)区......
  • oracle学习笔记(13)——数据库的启动与关闭
    1、常用的服务(1)OracleServiceSID     数据库服务,这个服务会自动地启动和停止数据库。如果安装了一个数据库,它的缺省启动类型为自动。服务进程为ORACLE.EXE,参数文件initSID.ora,日志文件SIDALRT.log,控制台SVRMGRL.EXE、SQLPLUS.EXE。     注:SID-数据库标识 ......
  • oracle学习笔记(10)——逻辑存储结构——段
    段是由一个或多个扩展区组成的逻辑存储单元,数据库模式对象在逻辑上是以段来占据表空间的大小,段代表特定数据类型的数据存储结构。1、 段的类型    段分为:数据段、索引段、临时段、回滚段    1)数据段       数据段用来存储表或簇的数据,可以细分为表......
  • oracle学习笔记(14)——安全管理
        数据库的安全性主要包括两个方面的含义:一方面是防止非法用户对数据库的访问,未授权的用户不能登录数据库;另一方面是每个数据库用户都有不同的操作权限,只能进行自己权限范围内的操作。Oracle数据库的安全可以分为两类:    1)系统安全性       系统安全......
  • oracle学习笔记(12)——数据库服务器工作模式与数据字典
    1、 专用服务器工作模式    1)概念:       专用服务器模式是指Oracle为每个用户进程启动一个专门的服务器进程,该服务器进程仅为该用户进程提供服务,直到用户进程断开连接时,对应的服务器进程才终止。    2)特点:       服务器进程与客户进......
  • Horizon学习笔记
    Horizon吊炸天!之前,一直认为horizon只不过是一个面板,没啥好研究的,而且我对django又不是很熟,一直懒的看horizon,今晚硬着头皮看了下去,没想到,越看越有劲,眼睛差点跟不上我的思路了!我觉得horizon牛不在对django的运用,而是对事物高度的抽象能力:D程序的入口点在horizon/openstack_dashboar......
  • 「Log」2023.8.26 小记
    序幕起晚了,干脆破罐子破摔,晚点到。八点前到校,被教练投喂雪糕。水两道红题,选笔记本巴拉巴拉。讲题讲题胡题。吃饭。讲题讲题胡题。摆。摆。摆。摆。摆。摆。摆。摆。摆。......
  • 27面向对象(继承)
    动态方法与静态方法#动态方法1.绑定给对象的方法classStudent:defrun(self):prtin(self)#类调用绑定给对象的方法:有几个参数就需要传几个参数Student.run(123)#对象调用绑定给对象的方法:会自动将对象当做第一个参数传入......