首页 > 其他分享 >状压dp

状压dp

时间:2023-12-21 19:13:20浏览次数:25  
标签:PII typedef int 状压 long include dp define

状压dp

image.png

暴力

枚举每一天摸不摸鱼, 对于每一组方案, 我们都可以判断其可不可行, 从可行方案中选择快乐值总和最大的一组;

复杂度\(O(2^{20})\)

每一组方案可以用 一个长度为n的二进制串来表示; 从右到左第i个位置表示第i天摸不摸鱼(1表示, 0表示不摸)

当n=5时, 10111表示在1, 2, 3, 5天摸鱼, 第4天不摸;

n个数位的二进制数字可以和\([0, 2^n)\)内十进制转换;

时间复杂度\(O(N^22^n)\)

#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 22;
int n, a[N], l[N], h[N][N], b[N];
int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  for (int i = 1; i <= n; i++) {
    scanf("%d", &l[i]);
    for (int j = 1; j <= l[i]; j++) scanf("%d", &h[i][j]);
  }
  int ans = 0;
  for (int i = 0; i < 1 << n; i++) {
    for (int j = 1, k = i; j <= n; j++, k >>= 1) b[j] = k & 1;
    bool ok = true;
    for (int j = 1; j <= n && ok; j++) {
      if (b[j] && l[j]) {
        int cnt = 0;
        for (int k = 1; k <= l[j]; k++) {
          if (b[h[j][k]]) cnt++;
        }
        if (cnt == l[j]) ok = false;
      }
    }
    if (!ok) continue;
    int res = 0;
    for (int j = 1; j <= n; j++)
      if (b[j]) res += a[j];
    ans = max(ans, res);
  }
  printf("%d\n", ans);

  return 0;
}

优化

判断一组方案x可不可行的时候可以不那么麻烦

定义s[i]为第i天摸鱼时计划表组成的二进制数,计划表上第一天摸鱼那么二进制数相应位置为1

对于第i天, 只要\(s_{i} \& x \neq s_{i}\)即可

当\(s_{i}\&x=s_{i}\)时意味着x与s[i]二进制位相应位置都为1, 那么方案不可行

时间复杂度\(O(n2^{n})\)

#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
typedef long long LL;
const int N = 22;
int n, a[N], l[N], h[N], b[N];
int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
  for (int i = 1; i <= n; i++) {
    scanf("%d", &l[i]);
    for (int j = 1; j <= l[i]; j++) {
      int x;
      scanf("%d", &x);
      h[i] |= (1 << (x - 1));
    }
  }
  int ans = 0;
  for (int i = 0; i < 1 << n; i++) {
    for (int j = 1, k = i; j <= n; j++, k >>= 1) b[j] = k & 1;
    bool ok = true;
    for (int j = 1; j <= n && ok; j++) {
      if (b[j] && l[j] && (i & h[j]) == h[j]) ok = false;
    }
    if (!ok) continue;
    int res = 0;
    for (int j = 1; j <= n; j++)
      if (b[j]) res += a[j];
    ans = max(ans, res);
  }
  printf("%d\n", ans);

  return 0;
}

image.png

考虑如何搜索, 在搜索过程中需要记录在之前的旅途中已经去过哪些城市, 现在在哪个城市, 到目前为止一共花费多少时间;

在之前的旅途中去过的城市的顺序时不关键的, 用\(f[i][j]\)表示去过城市的状态是i(i记录哪些城市去过, 哪些城市没去过), 现在在j号城市的情况下最少花费了多少时间;

用一个长度为n的二进制字符串记录; 从右到左第x个位置表示蜗蜗没去过x号城市(1表示去过, 0表示没去过)

n个数位的二进制数字可以和\([0, 2^n)\)内十进制转换;

转移时,一定是从i小的状态转移到i大的状态

答案 $$
\max_{2\leq j \leq n}{f[2^{n}-1][j]+a[j][1]}

\[前面所有城市已经不重复走过再从任意与1相通的的城市走进1 时间复杂度$O(n^{2}2^{n})$ ```cpp #include <algorithm> #include <array> #include <cmath> #include <cstring> #include <iostream> #include <map> #include <queue> #include <set> #include <stack> #include <vector> using namespace std; #define x first #define y second typedef pair<int, int> PII; typedef long long LL; const int N = 22; int n, a[N][N], f[1 << 18][19]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) scanf("%d", &a[i][j]); memset(f, 0x3f, sizeof(f)); f[1][1] = 0; // 枚举所有情况 for (int i = 1; i < 1 << n; i++) { // 在走过i的二进制表示为1的城市后现在在j城市 for (int j = 1; j <= n; j++) { // 只有当f[i][j]有路径时才可以继续, // 当i的二进制位第j位为0时意味着这条路径不存在 if (f[i][j] < 1 << 30) { // 枚举下一个到达的城市 for (int k = 1; k <= n; k++) { // i & (1 << (k - 1)) 为0时意味着当前方案并没有走过城市k, 可以走这条路 if (!(i & (1 << (k - 1)))) { f[i + (1 << (k - 1))][k] = min(f[i + (1 << (k - 1))][k], f[i][j] + a[j][k]); } } } } } int ans = 1 << 30; for (int i = 2; i <= n; i++) ans = min(ans, f[(1 << n) - 1][i] + a[i][1]); printf("%d\n", ans); return 0; } ``` ![](https://raw.githubusercontent.com/XuandYu000/picture/main/picture/202309182114110.png) 用f[i]表示现在还没有被消除的方块的状态为i(i记录哪些方块已经被消除了, 哪些方块还没有被消除)的情况下最小需要花费多少代价; 接下来枚举哪个/哪两个方块, 就可以转移了 用一个长度为n的二进制字符串记录; 从右往左第x个位置表示第x个方块有没有被消除(1表示被消除了, 0表示没有被消除); 有n个数位的二进制数字可以和一个$[0, 2^n]$内的十进制数字互相转换 转移时一定是从i小的状态转移到i大的状态 最后答案等于$f[2^n-1]$, 时间复杂度$O(n^22^{n})$ ```cpp #include <algorithm> #include <array> #include <cmath> #include <cstring> #include <iostream> #include <map> #include <queue> #include <set> #include <stack> #include <vector> using namespace std; #define x first #define y second typedef pair<int, int> PII; typedef long long LL; const int N = 22; int n, a[N], f[1 << 20]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); memset(f, 0x3f, sizeof(f)); f[0] = 0; // 枚举所有情况, 从右往左第j位表示i的二进制表示所代表的方案在消除了第j块方块 for (int i = 0; i < 1 << n; i++) { // 枚举当前这一步要消除的方块 for (int j = 1; j <= n; j++) { // 当前想要消除的方块没有被消 if (!(i & (1 << (j - 1)))) { f[i + (1 << (j - 1))] = min(f[i + (1 << (j - 1))], f[i] + a[j]); // 找第二块被消除的方块 for (int k = j + 1; k <= n; k++) { if (!(i & (1 << (k - 1)))) f[i + (1 << (j - 1)) + (1 << (k - 1))] = min( f[i + (1 << (j - 1)) + (1 << (k - 1))], f[i] + (a[j] ^ a[k])); } } } } printf("%d", f[(1 << n) - 1]); return 0; } ``` #### 优化 对于一个状态i, 优先消除编号最小的没有被消除的方块 时间复杂度$O(n2^n)$ ```cpp #include <algorithm> #include <array> #include <cmath> #include <cstring> #include <iostream> #include <map> #include <queue> #include <set> #include <stack> #include <vector> using namespace std; #define x first #define y second typedef pair<int, int> PII; typedef long long LL; const int N = 22; int n, a[N], f[1 << 20]; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); memset(f, 0x3f, sizeof(f)); f[0] = 0; // 枚举所有情况, 从右往左第j位表示i的二进制表示所代表的方案在消除了第j块方块 for (int i = 0; i < 1 << n; i++) { // 枚举当前这一步要消除的方块 for (int j = 1; j <= n; j++) { // 当前想要消除的方块没有被消 if (!(i & (1 << (j - 1)))) { f[i + (1 << (j - 1))] = min(f[i + (1 << (j - 1))], f[i] + a[j]); // 找第二块被消除的方块 for (int k = j + 1; k <= n; k++) { if (!(i & (1 << (k - 1)))) f[i + (1 << (j - 1)) + (1 << (k - 1))] = min( f[i + (1 << (j - 1)) + (1 << (k - 1))], f[i] + (a[j] ^ a[k])); } // 对只在这里加个break防止重复 break; } } } printf("%d", f[(1 << n) - 1]); return 0; } ``` ![image.png](https://raw.githubusercontent.com/XuandYu000/picture/main/picture/202309182150838.png) 对于第i天, 我们只要知道从第i天开始往左数m天每天吃不吃麦当劳就可以了 用$f[i][j]$表示第i天, 最近m天每天吃不吃麦当劳的状态为j(j记录了这m天中哪些天吃了麦当劳, 那些天没吃)的情况下快乐值最大是多少; j用一个长度为m的二进制字符串表示, 从右往左第x位置表示第$i - (m - x)$天有没有吃麦当劳(1表示吃了, 0没吃) $f[i][j]$可以转移到$f[i + 1][j']$ i变成了i+1, j需要先除以2; 然后枚举第i天吃不吃麦当劳即可 - 吃, 状态加$2^{m - 1}$ 时间复杂度$O(n2^m)$ ```cpp #include <algorithm> #include <array> #include <cmath> #include <cstring> #include <iostream> #include <map> #include <queue> #include <set> #include <stack> #include <vector> using namespace std; #define x first #define y second typedef pair<int, int> PII; typedef long long LL; int n, m, f[256], a[100001], v[256]; bool b[256]; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 0; i < 1 << m; i++) { int cnt = 0; for (int j = 0; j < m; j++) { if (i & (1 << j)) cnt++; } if (cnt <= m / 2) b[i] = true; } memset(f, -1, sizeof(f)); f[0] = 0; for (int i = 1; i <= n; i++) { memset(v, -1, sizeof(v)); for (int j = 0; j < 1 << m; j++) { if (f[j] >= 0) { v[j / 2] = max(v[j / 2], f[j]); if (b[j / 2 + (1 << (m - 1))]) v[j / 2 + (1 << (m - 1))] = max(v[j / 2 + (1 << (m - 1))], f[j] + a[i]); } } memcpy(f, v, sizeof(f)); } int ans = 0; for (int i = 0; i < 1 << m; i++) { ans = max(ans, f[i]); } printf("%d\n", ans); return 0; } ``` ![image.png](https://raw.githubusercontent.com/XuandYu000/picture/main/picture/202309182229561.png) ![image.png](https://raw.githubusercontent.com/XuandYu000/picture/main/picture/202309182231004.png) 当x, y固定的时候, 使用一个$<2^m$的数字表示轮廓线(从右往左第i个位置为1表示第i列存在绿色格子, 0则不存在) $f[x][y][k]$表示考虑到了第x行第y列的格子, 轮廓线的二进制表示为k时的方案数 ![image.png](https://raw.githubusercontent.com/XuandYu000/picture/main/picture/202309182235332.png) 答案等于$f[n][m][0]$ ```cpp #include <algorithm> #include <array> #include <cmath> #include <cstring> #include <iostream> #include <map> #include <queue> #include <set> #include <stack> #include <vector> using namespace std; #define x first #define y second typedef pair<int, int> PII; typedef long long LL; int n, m, f[1 << 18], v[1 << 18]; const int p = 1e9 + 7; int main() { scanf("%d%d", &n, &m); memset(f, 0, sizeof(f)); f[0] = 1; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { memset(v, 0, sizeof(v)); for (int k = 0; k < 1 << m; k++) { if (f[k]) { if (k & (1 << (j - 1))) v[k - (1 << (j - 1))] += f[k], v[k - (1 << (j - 1))] %= p; else { if (i != n) v[k + (1 << (j - 1))] += f[k], v[k + (1 << (j - 1))] %= p; if (j != m && !(k & (1 << j))) v[k + (1 << j)] += f[k], v[k + (1 << j)] %= p; } } } memcpy(f, v, sizeof(f)); } } printf("%d\n", f[0]); return 0; } ```\]

标签:PII,typedef,int,状压,long,include,dp,define
From: https://www.cnblogs.com/viewoverlooking/p/17919908.html

相关文章

  • k8s~ingress_service_endpoint_pod四壮士
    在Kubernetes中,Service和Endpoints是两个重要的概念,它们之间存在着密切的关系。Service:Service是Kubernetes中用于定义一组Pod的访问方式的抽象。通过创建Service,可以为一组具有相同标签的Pod提供统一的访问入口,使得客户端可以通过Service来访问这些Pod,而无需了解其具体的IP地......
  • class080 状压dp-上【算法】
    class080状压dp-上【算法】算法讲解080【必备】状压dp-上Code1464.我能赢吗//我能赢吗//给定两个整数n和m//两个玩家可以轮流从公共整数池中抽取从1到n的整数(不放回)//抽取的整数会累加起来(两个玩家都算)//谁在自己的回合让累加和>=m,谁获胜//若先出手的玩家能稳赢则......
  • class081 状压dp-下【算法】
    class081状压dp-下【算法】算法讲解081【必备】状压dp-下Code11434.每个人戴不同帽子的方案数//每个人戴不同帽子的方案数//总共有n个人和40种不同的帽子,帽子编号从1到40//给你一个整数列表的列表hats,其中hats[i]是第i个人所有喜欢帽子的列表//请你给每个人......
  • class073 背包dp-01背包、有依赖的背包【算法】
    class073背包dp-01背包、有依赖的背包【算法】算法讲解073【必备】背包dp-01背包、有依赖的背包code1P1048[NOIP2005普及组]采药//01背包(模版)//给定一个正数t,表示背包的容量//有m个货物,每个货物可以选择一次//每个货物有自己的体积costs[i]和价值values[i]//返回在......
  • class074 背包dp-分组背包、完全背包【算法】
    class074背包dp-分组背包、完全背包【算法】算法讲解074【必备】背包dp-分组背包、完全背包code1P1757通天之分组背包//分组背包(模版)//给定一个正数m表示背包的容量,有n个货物可供挑选//每个货物有自己的体积(容量消耗)、价值(获得收益)、组号(分组)//同一个组的物品只......
  • 线性DP
    线性DP例题:POJ2279思考:考虑dp_{i,j,k}表示第i行,第j列,安排k去站的方案数。错误原因:安排k去站但是可能会造成重复选择k。正解:考虑dp_{a1,a2,a3,a4,a5}表示各排从左边起分别站了a1,a2,a3,a4,a5个人时,合影方案数。疑惑:Q1.为什么不用考虑某个位置究竟站的......
  • Flink处理函数解析(ProcessFunction和KeyedProcessFunction)
    Flink中的处理函数(ProcessFunction和KeyedProcessFunction)在对于数据进行颗粒化的精确计算时使用较多,处理函数提供了一个定时服务(TimerService),可以向未来注册一个定时服务,我们可以把它理解为一个闹钟,当闹钟响起时,就调用ProcessFunction中的onTimer()方法,会对数据进行一些计算。我......
  • 金牌导航-数据结构优化DP
    数据结构优化DP例题A题解设\(f_{i,j}\)表示以第\(i\)位为结尾,长度为\(j\)的严格单调上升子序列的数量。那么显然有\(f_{i,j}=\sum_{k=1}^{i-1}f_{k,j-1}\times(a_k<a_i)\)然后发现这玩应\(O(n^2m)\)直接寄掉了。考虑优化。发现只有当\(a_k<a_i\)时才会有贡献。......
  • TQX 的 DP AAgain!
    闲话:这确实抽象,将所有人给干离线了……不如叫做TQX的离线DPQwQDP基本思路就是找一个比较好的能够描绘问题的状态,想怎么转移,再进行优化。--TQX背包DPloj6089.小Y的背包计数问题根号分治优化背包,大概就是利用\(cnt\timessiz\gecap\)将多重变为完全,然后统......
  • wordpress博客系统
    wordpress博客系统LNMP:Linux+nginx+mysql+php一个操作系统+web网站+一个数据库存放数据+后端编程语言基于红帽操作系统来搭建1.需要一个本地yum仓库[[email protected]]#vimlocal.repo[local]name=localbaseurl=file:///mediaenabled=1gpgcheck=0[root@ser......