首页 > 其他分享 >学不会的动态规划——状压DP

学不会的动态规划——状压DP

时间:2023-09-22 17:34:26浏览次数:61  
标签:状态 int 状压 DP 集合 动态 dp

前言

不知道为什么越是接近网络赛就越是静不下心来,可能也是因为开学了吧,QAQ,有一说一还是暑假比较适合训练。第一场网络赛,特意选了一个属于我们队的“风水宝地”(其实是我们去的早获得了优先选择权),但是好像并没有什么用,读题读巨慢,还被签到卡了2h(大概,有点不记得了),最后开j,队友推公式写了一手,发现错了,准备调的时候,队友讨论了一下,说结论假了QAQ。感觉不能放任自己这样下去,在dalao的建议下,决定写写DP,又想到第一场网络赛的I要状压DP,那就状压!!!

定义

状态压缩动态规划,顾名思义就是: 状态压缩 + 动态规划,即通过状态压缩来达到优化的目的

引入

Hamilton问题

题目描述

给定一张 \(n\) 个点的带权无向图,点从 \(0 \sim n-1\) 标号,求起点 \(0\) 到终点 \(n-1\) 的最短 Hamilton 路径。
Hamilton 路径的定义是从 \(0\) 到 \(n-1\) 不重不漏地经过每个点恰好一次。

题目分析

一眼看上去,呀!这不就是除了首位以外的全排列嘛!某人(指自己)很容易想到用深搜 + 最优化剪枝,然后某人得到

T了之后某人才开始算时间复杂度(这是不对的!应该在写程序之前就计算好,要不然就会像某人一样,写一个绝对不可能AC的代码),不难发现,时间复杂度为O(n!),显而易见,这是不可行的。

虽然很难想到(bushi),但是这题我们可以通过DP,再加上状态压缩的优化来解决,能将复杂度降低到O(\(n^2 * 2^n\))。

1、首先,我们来设计状态,我们可以把走过和没走过i点分别标记为1/0,不妨设dp[][],第一维表示:当前所有点的状态(是否被走过),第二维表示:当前状态下的终点

2、然后,我们来思考状态转移方程,不难想到dp[11][2] = std::min({dp[10][1] + dis[1][2] , dp[01][0] + dis[0][2]}),在这个基础上,我们用符号来表示就是:dp[s][j] = std::min(dp[s - j][k] + dis[k][j] , dp[s][j]);

3、因为起点是固定的,并且我们要求最短的Hamilton路径,所以我们将dp数组初始化为最大值,dp[1][0] = 0;

分析好了后,我们不难写出代码

 for(int s = 1;s < (1 << n);s ++ ){
    for(int j = 0;j < n;j ++){
        if((s >> j) & 1){//当前集合包含j
            for(int k = 0;k < n;k ++){//s-j包含k
                if ((s ^ (1 << j)) >> k & 1){
                    dp[s][j] = std::min(dp[s][j],dp[s ^ (1  << j)][k] + ans[k][j]);
                }
            }
        }
    }
}

只有当集合包含j时,才存在j为终点;s-j集合包含k,即表示在j没有被走过的集合状态下,集合包含k。状态转移方程表示:j没有被走过的集合状态下,以k为终点的路径,再从k走到j的值,与dp[s][j]原来的值比较,取值其中的小者。

原理

状态dp的应用背景是以集合为状态,用二进制来表示集合中元素的状态。

状压dp的思想主要是:用二进制表示集合的状态,并用二进制的位运算遍历和操作,操作简单并且速度快。

但是需要注意的是,状态压缩只是一种DP处理集合的工具,时间复杂度主要还是取决于dp算法本身,与是否使用状态压缩关系不大。

一些例子

方格取数(1)

Problem Description

给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。

Input

包括多个测试实例,每个测试实例包括一个整数n 和n*n个非负数(n<=20)

Output

对于每个测试实例,输出可能取得的最大的和

Sample Input

3
75 15 21 
75 15 28 
34 70 5 

Sample Output

188

Author

ailyanlu

我的代码

#include <bits/stdc++.h>
//#define int long long
int dp[2][(1 << 20) + 1];
int a[21][21];
int tot[(1 << 20) + 1];
int cal(int i , int x){
    int cnt = 1 , res = 0;
    while (x){
        if(x & 1) res += a[i][cnt];
        x /= 2;
        cnt ++;
    }
    return res;
}
signed main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);std::cout.tie(0);
    int n;
    while (std::cin >> n){
        
        for(int i = 0 ; i < 2 ; i ++){
            for(int j = 0 ; j <= (1 << 20) ; j ++){
                dp[i][j] = 0;
            }
        }
        
        for(int i = 1 ; i <= n ; i ++){
            for(int j = 1 ; j <= n ; j ++){
                std::cin >> a[i][j];
            }
        }

        int cnt = 0;
        for(int i = 0 ; i < (1 << n) ; i ++){
            if((i & (i >> 1)) == 0) {//如果i & i >> 1,说明i一定没有相邻的两个1
                tot[++cnt] = i;
            }
        }

        for(int i = 1 ; i <= n ; i ++){//每一行
            for(int j = 1 ; j <= cnt ; j ++){
                int val = cal(i , tot[j]);//算出第i行,取j状态时
                for(int k = 1 ; k <= cnt ; k ++){
                    if(!(tot[j] & tot[k])) {//因为当前行的状态只跟上一行有关,可以用滚动数组优化空间
                        dp[i % 2][k] = std::max(dp[i % 2][k], dp[(i - 1) % 2][j] + val);
                    }
                }
            }
        }
        
        int max = 0;
        for(int i = 0 ; i <= cnt ; i ++){
            max = std::max(max , dp[n % 2][i]);
        }
        std::cout << max << "\n";
    }
    return 0;
}

题目分析

我们可以使用二进制来表示某一行的选择状态,即状态压缩。如果i & (i- 1) == 0,则说明在二进制状态下相邻的位置不会同时为1。在每一行的位置,根据上一行的情况,尝试所有的可能情况,因为空间的限制,我们可以使用滚动数组来进行优化。

[「SCOI2005」互不侵犯] (https://loj.ac/p/2153)

题目描述

在 \(N \times N\) 的棋盘里面放 \(K\) 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共 \(8\) 个格子。

输入格式

只有一行,包含两个数 \(N\), \(K\)。

输出格式

所得的方案数。

样例

输入

3 2

输出

16

我的代码

#include <bits/stdc++.h>
#define int long long
int tot[(1 << 10)];
int dp[10][(1 << 10)][100];
int cnt;

int cal(int x){//当前状态能放多少个棋子
    int res = 0;
    while (x){
        if(x & 1) res ++;
        x /= 2;
    }
    return res;
}

void solve(){
    int n , k;
    std::cin >> n >> k;

    for(int i = 0 ; i < (1 << n) ; i ++){//计算在横向满足条件的状态
        if((i & (i >> 1)) == 0) tot[++ cnt] = i;
    }

    int ans = 0;

    for(int i = 1 ; i <= cnt ;i ++){//初始化第一行的状态
        dp[1][i][cal(tot[i])] ++;
    }

    for(int i = 2 ; i <= n ; i ++){//第i行
        for(int j = 1 ; j <= cnt ; j ++){//第i - 1行是j状态时
            for(int p = 1 ; p <= cnt ; p ++){//第i行时p状态
                int val = cal(tot[p]);//p状态能放的棋子数
                //判断在第i - 1行为j状态下,第i行是否能放p状态的棋子
                if((tot[p] & tot[j]) == 0 && (((tot[p] << 1) & tot[j]) == 0) && (((tot[p] >> 1) & tot[j]) == 0)) {
                    for(int l = val ; l <= k ; l ++) {//当第i行为p状态时,放了l个棋子的情况有几种
                        dp[i][p][l] += dp[(i - 1)][j][l - val];
                    }
                }
            }
        }
    }

    for(int i = 1 ; i <= cnt ; i ++){//最后一行为i状态时,棋子数为k的方案数
        ans += dp[n][i][k];
    }
    std::cout << ans << '\n';

}
signed main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);std::cout.tie(0);

    int t = 1;
//    std::cin >> t;
    while (t --){
        solve();
    }
    return 0;
}

标签:状态,int,状压,DP,集合,动态,dp
From: https://www.cnblogs.com/clearTea/p/17714228.html

相关文章

  • 中文图形验证码 动态图形验证码 图片验证码 验证码【加逻辑思路解析】
    效果: 逻辑:生成数字随机数,再改为中文表示,返给前端。人为输入阿拉伯数字。(后端缓存中存入用户信息和随机数。做校验。)主要测试code:Randomrm=newRandom();Stringstrcode=Integer.toString(rm.nextInt(900000)+100000);System.out.println("生成......
  • Day05 - Vue之动态组件、插槽、项目的创建
    动态组件//关键字: component//使用方法:<component:is="who"></component>//component标签的is属性等于组件名字,这里就会显示这个组件<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><link......
  • Exam DP-300: Administering Microsoft Azure SQL Solutions 微软Azure SQL Solutions
    作为该考试的考生,您应具备构建数据库解决方案方面的主题专业知识,这些解决方案旨在支持使用数据库构建的多种工作负载:企业内部SQLServerAzureSQL服务您是一名数据库管理员,负责管理使用SQLServer和AzureSQL服务构建的内部部署和云数据库。作为Azure数据库管理员,您......
  • 表格动态合并,序号连续
    效果:<el-table:data="tableData"style="width:100%"v-loading="tableDataLoading":header-cell-style="{background:'#FAFAFA'}":span-method="objectSpanMethod"......
  • 在选择屏幕中,根据按钮动态显示时,如果忘记写USER-COMMAND时会发生的问题
    顾问要求在,选择屏幕单据查询时显示成圈线和生产线选择框,在明细查询时隐藏,听需求是一个很简单的选择屏幕隐藏的功能,实现代码如下PARAMETERS:p_djRADIOBUTTONGROUPcxDEFAULT'X',"单据查询p_mxRADIOBUTTONGROUPcx."......
  • window和linux下有关xxx.dll和xxx.so动态库,可执行文件运行时的动态库检索路径文档
    没想到详细的内容都在库和命令的man手册中。ld.so动态库手册里有描述ELF可执行文件在运行时,都会在哪几个位置检索动态库。如果共享对象依赖项不包含斜杠,则它按以下顺序搜索:(1)使用二进制文件的DT_RPATH动态节属性中指定的目录(如果存在且DT_RUNPATH属性不存在)。不推荐......
  • Cygwin 编译的动态库文件.dll.a
    前提Cygwin编译OpenSSL出来的有两种文件:libcrypto.a和libcrypto.dll.a,VS编译调用没有问题,运行卡住,暂时未解决测试代码#include<openssl/evp.h>intmain(intargc,char*argv[]){ EVP_MD_CTX*mdctx; mdctx=EVP_MD_CTX_new(); EVP_MD_CTX_init(mdctx); EVP_DigestInit(m......
  • selenium自动化测试-获取动态页面小说
    有的网站页面是动态加载的资源,使用bs4库只能获取静态页面内容,无法获取动态页面内容,通过selenium自动化测试工具可以获取动态页面内容。参考之前的"bs4库爬取小说工具"文章代码,稍微修改下,就可以转成获取动态页面小说工具。第一步:先确定目标网址先找到小说目录页面。网址首页:'ht......
  • win10操作系统动态链接库DLL文件搜索路径
    搜索可执行文件(xx.exe)同级目录下的其它DLL文件(不会搜索子文件夹)32位程序C:\Windows\System32操作系统当前用户或者系统用户Path环境变量中直接包含的文件夹(子文件夹中的DLL同样无法被搜索到,不是递归搜索)在终端执行D:\code>C:\Users\XXX\Desktop\新建文件夹\bb.......
  • 小白也能看懂的插件化DroidPlugin原理(三)-- 如何拦截startActivity方法
    **前言:**在前两篇文章中分别介绍了动态代理、反射机制和Hook机制,如果对这些还不太了解的童鞋建议先去参考一下前两篇文章。经过了前面两篇文章的铺垫,终于可以玩点真刀实弹的了,本篇将会通过Hook掉startActivity方法的一个小例子来介绍如何找出合适的Hook切入点。开始之前我们......