算法数据结构(三十七)----状态压缩技巧

2024-06-13 12:18

本文主要是介绍算法数据结构(三十七)----状态压缩技巧,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

题目一

链接:https://leetcode-cn.com/problems/can-i-win

在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到或超过 100 的玩家,即为胜者。

如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?

你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。

// 1~choose 拥有的数字// total 一开始的剩余// 返回先手会不会赢public static boolean canIWin0(int choose, int total) {if (total == 0) {return true;}if ((choose * (choose + 1) >> 1) < total) {return false;}int[] arr = new int[choose];for (int i = 0; i < choose; i++) {arr[i] = i + 1;}// arr[i] != -1 表示arr[i]这个数字还没被拿走// arr[i] == -1 表示arr[i]这个数字已经被拿走// 集合,arr,1~choosereturn process(arr, total);}// 当前轮到先手拿,// 先手只能选择在arr中还存在的数字,// 还剩rest这么值,// 返回先手会不会赢public static boolean process(int[] arr, int rest) {if (rest <= 0) {return false;}// 先手去尝试所有的情况for (int i = 0; i < arr.length; i++) {if (arr[i] != -1) {int cur = arr[i];arr[i] = -1;boolean next = process(arr, rest - cur);arr[i] = cur;if (!next) {return true;}}}return false;}
// 暴力尝试public static boolean canIWin1(int choose, int total) {if (total == 0) {return true;}if ((choose * (choose + 1) >> 1) < total) {return false;}return process1(choose, 0, total);}// 当前轮到先手拿,// 先手可以拿1~choose中的任何一个数字// status   i位如果为0,代表没拿,当前可以拿//          i位为1,代表已经拿过了,当前不能拿// 还剩rest这么值,// 返回先手会不会赢public static boolean process1(int choose, int status, int rest) {if (rest <= 0) {return false;}for (int i = 1; i <= choose; i++) {if (((1 << i) & status) == 0) { // i 这个数字,是此时先手的决定!if (!process1(choose, (status | (1 << i)), rest - i)) {return true;}}}return false;}
// 暴力尝试改动态规划public static boolean canIWin2(int choose, int total) {if (total == 0) {return true;}if ((choose * (choose + 1) >> 1) < total) {return false;}int[] dp = new int[1 << (choose + 1)];// dp[status] == 1  true// dp[status] == -1  false// dp[status] == 0  process(status) 没算过!去算!return process2(choose, 0, total, dp);}// 为什么明明status和rest是两个可变参数,却只用status来代表状态(也就是dp)// 因为选了一批数字之后,得到的和一定是一样的,所以rest是由status决定的,所以rest不需要参与记忆化搜索public static boolean process2(int choose, int status, int rest, int[] dp) {if (dp[status] != 0) {return dp[status] == 1 ? true : false;}boolean ans = false;if (rest > 0) {for (int i = 1; i <= choose; i++) {if (((1 << i) & status) == 0) {if (!process2(choose, (status | (1 << i)), rest - i, dp)) {ans = true;break;}}}}dp[status] = ans ? 1 : -1;return ans;}

题目二

 TSP问题 有N个城市,任何两个城市之间的都有距离,任何一座城市到自己的距离都为0。所有点到点的距 离都存在一个N*N的二维数组matrix里,也就是整张图由邻接矩阵表示。现要求一旅行商从k城市 出发必须经过每一个城市且只在一个城市逗留一次,最后回到出发的k城,返回总距离最短的路的 距离。参数给定一个matrix给定k

 

public static int t1(int[][] matrix) {int N = matrix.length; // 0...N-1// set// set.get(i) != null i这座城市在集合里// set.get(i) == null i这座城市不在集合里List<Integer> set = new ArrayList<>();for (int i = 0; i < N; i++) {set.add(1);}return func1(matrix, set, 0);}// 任何两座城市之间的距离,可以在matrix里面拿到// set中表示着哪些城市的集合,// start这座城一定在set里,// 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少public static int func1(int[][] matrix, List<Integer> set, int start) {int cityNum = 0;for (int i = 0; i < set.size(); i++) {if (set.get(i) != null) {cityNum++;}}if (cityNum == 1) {return matrix[start][0];}// cityNum > 1  不只start这一座城set.set(start, null);int min = Integer.MAX_VALUE;for (int i = 0; i < set.size(); i++) {if (set.get(i) != null) {// start -> i i... -> 0int cur = matrix[start][i] + func1(matrix, set, i);min = Math.min(min, cur);}}set.set(start, 1);return min;}
//暴力递归
public static int t2(int[][] matrix) {int N = matrix.length; // 0...N-1// 7座城 1111111int allCity = (1 << N) - 1;return f2(matrix, allCity, 0);}// 任何两座城市之间的距离,可以在matrix里面拿到// set中表示着哪些城市的集合,// start这座城一定在set里,// 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少public static int f2(int[][] matrix, int cityStatus, int start) {// cityStatus == cityStatux & (~cityStaus + 1)if (cityStatus == (cityStatus & (~cityStatus + 1))) {return matrix[start][0];}// 把start位的1去掉,cityStatus &= (~(1 << start));int min = Integer.MAX_VALUE;// 枚举所有的城市for (int move = 0; move < matrix.length; move++) {if ((cityStatus & (1 << move)) != 0) {int cur = matrix[start][move] + f2(matrix, cityStatus, move);min = Math.min(min, cur);}}cityStatus |= (1 << start);return min;}
public static int t3(int[][] matrix) {int N = matrix.length; // 0...N-1// 7座城 1111111int allCity = (1 << N) - 1;int[][] dp = new int[1 << N][N];for (int i = 0; i < (1 << N); i++) {for (int j = 0; j < N; j++) {dp[i][j] = -1;}}return f3(matrix, allCity, 0, dp);}// 任何两座城市之间的距离,可以在matrix里面拿到// set中表示着哪些城市的集合,// start这座城一定在set里,// 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少public static int f3(int[][] matrix, int cityStatus, int start, int[][] dp) {if (dp[cityStatus][start] != -1) {return dp[cityStatus][start];}if (cityStatus == (cityStatus & (~cityStatus + 1))) {dp[cityStatus][start] = matrix[start][0];} else {// 把start位的1去掉,cityStatus &= (~(1 << start));int min = Integer.MAX_VALUE;// 枚举所有的城市for (int move = 0; move < matrix.length; move++) {if (move != start && (cityStatus & (1 << move)) != 0) {int cur = matrix[start][move] + f3(matrix, cityStatus, move, dp);min = Math.min(min, cur);}}cityStatus |= (1 << start);dp[cityStatus][start] = min;}return dp[cityStatus][start];}
public static int t4(int[][] matrix) {int N = matrix.length; // 0...N-1int statusNums = 1 << N;int[][] dp = new int[statusNums][N];for (int status = 0; status < statusNums; status++) {for (int start = 0; start < N; start++) {if ((status & (1 << start)) != 0) {if (status == (status & (~status + 1))) {dp[status][start] = matrix[start][0];} else {int min = Integer.MAX_VALUE;// start 城市在status里去掉之后,的状态int preStatus = status & (~(1 << start));// start -> ifor (int i = 0; i < N; i++) {if ((preStatus & (1 << i)) != 0) {int cur = matrix[start][i] + dp[preStatus][i];min = Math.min(min, cur);}}dp[status][start] = min;}}}}return dp[statusNums - 1][0];}

 题目三

你有无限的1*2的砖块,要铺满M*N的区域,

不同的铺法有多少种?

public static int ways1(int N, int M) {if (N < 1 || M < 1 || ((N * M) & 1) != 0) {return 0;}if (N == 1 || M == 1) {return 1;}int[] pre = new int[M]; // pre代表-1行的状况for (int i = 0; i < pre.length; i++) {pre[i] = 1;}return process(pre, 0, N);}// pre 表示level-1行的状态// level表示,正在level行做决定// N 表示一共有多少行 固定的// level-2行及其之上所有行,都摆满砖了// level做决定,让所有区域都满,方法数返回public static int process(int[] pre, int level, int N) {if (level == N) { // base casefor (int i = 0; i < pre.length; i++) {if (pre[i] == 0) {return 0;}}return 1;}// 没到终止行,可以选择在当前的level行摆瓷砖int[] op = getOp(pre);return dfs(op, 0, level, N);}// op[i] == 0 可以考虑摆砖// op[i] == 1 只能竖着向上public static int dfs(int[] op, int col, int level, int N) {// 在列上自由发挥,玩深度优先遍历,当col来到终止列,i行的决定做完了// 轮到i+1行,做决定if (col == op.length) {return process(op, level + 1, N);}int ans = 0;// col位置不横摆ans += dfs(op, col + 1, level, N); // col位置上不摆横转// col位置横摆, 向右if (col + 1 < op.length && op[col] == 0 && op[col + 1] == 0) {op[col] = 1;op[col + 1] = 1;ans += dfs(op, col + 2, level, N);op[col] = 0;op[col + 1] = 0;}return ans;}public static int[] getOp(int[] pre) {int[] cur = new int[pre.length];for (int i = 0; i < pre.length; i++) {cur[i] = pre[i] ^ 1;}return cur;}
// Min (N,M) 不超过 32public static int ways2(int N, int M) {if (N < 1 || M < 1 || ((N * M) & 1) != 0) {return 0;}if (N == 1 || M == 1) {return 1;}int max = Math.max(N, M);int min = Math.min(N, M);int pre = (1 << min) - 1;return process2(pre, 0, max, min);}// 上一行的状态,是pre,limit是用来对齐的,固定参数不用管// 当前来到i行,一共N行,返回填满的方法数public static int process2(int pre, int i, int N, int M) {if (i == N) { // base casereturn pre == ((1 << M) - 1) ? 1 : 0;}int op = ((~pre) & ((1 << M) - 1));return dfs2(op, M - 1, i, N, M);}public static int dfs2(int op, int col, int level, int N, int M) {if (col == -1) {return process2(op, level + 1, N, M);}int ans = 0;ans += dfs2(op, col - 1, level, N, M);if ((op & (1 << col)) == 0 && col - 1 >= 0 && (op & (1 << (col - 1))) == 0) {ans += dfs2((op | (3 << (col - 1))), col - 2, level, N, M);}return ans;}
// 记忆化搜索的解// Min(N,M) 不超过 32public static int ways3(int N, int M) {if (N < 1 || M < 1 || ((N * M) & 1) != 0) {return 0;}if (N == 1 || M == 1) {return 1;}int max = Math.max(N, M);int min = Math.min(N, M);int pre = (1 << min) - 1;int[][] dp = new int[1 << min][max + 1];for (int i = 0; i < dp.length; i++) {for (int j = 0; j < dp[0].length; j++) {dp[i][j] = -1;}}return process3(pre, 0, max, min, dp);}public static int process3(int pre, int i, int N, int M, int[][] dp) {if (dp[pre][i] != -1) {return dp[pre][i];}int ans = 0;if (i == N) {ans = pre == ((1 << M) - 1) ? 1 : 0;} else {int op = ((~pre) & ((1 << M) - 1));ans = dfs3(op, M - 1, i, N, M, dp);}dp[pre][i] = ans;return ans;}public static int dfs3(int op, int col, int level, int N, int M, int[][] dp) {if (col == -1) {return process3(op, level + 1, N, M, dp);}int ans = 0;ans += dfs3(op, col - 1, level, N, M, dp);if (col > 0 && (op & (3 << (col - 1))) == 0) {ans += dfs3((op | (3 << (col - 1))), col - 2, level, N, M, dp);}return ans;}
// 严格位置依赖的动态规划解public static int ways4(int N, int M) {if (N < 1 || M < 1 || ((N * M) & 1) != 0) {return 0;}if (N == 1 || M == 1) {return 1;}int big = N > M ? N : M;int small = big == N ? M : N;int sn = 1 << small;int limit = sn - 1;int[] dp = new int[sn];dp[limit] = 1;int[] cur = new int[sn];for (int level = 0; level < big; level++) {for (int status = 0; status < sn; status++) {if (dp[status] != 0) {int op = (~status) & limit;dfs4(dp[status], op, 0, small - 1, cur);}}for (int i = 0; i < sn; i++) {dp[i] = 0;}int[] tmp = dp;dp = cur;cur = tmp;}return dp[limit];}public static void dfs4(int way, int op, int index, int end, int[] cur) {if (index == end) {cur[op] += way;} else {dfs4(way, op, index + 1, end, cur);if (((3 << index) & op) == 0) { // 11 << index 可以放砖dfs4(way, op | (3 << index), index + 1, end, cur);}}}

这篇关于算法数据结构(三十七)----状态压缩技巧的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1057276

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

hdu1565(状态压缩)

本人第一道ac的状态压缩dp,这题的数据非常水,很容易过 题意:在n*n的矩阵中选数字使得不存在任意两个数字相邻,求最大值 解题思路: 一、因为在1<<20中有很多状态是无效的,所以第一步是选择有效状态,存到cnt[]数组中 二、dp[i][j]表示到第i行的状态cnt[j]所能得到的最大值,状态转移方程dp[i][j] = max(dp[i][j],dp[i-1][k]) ,其中k满足c

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO