位运算技巧(持续更新)

xiaoxiao2021-02-27  228

最近在补西安集训入门班的题,想想dsy和aijiao还有v8讲课的时候就提到过很多关于用二进制处理状态,用位运算处理问题的思路。大大简化了问题的难度。

大家可以自己搜索“acm 位运算优化”

当然这里提供北大大神的博客  位运算-Matrix67大神   。我看了这篇之后觉得明白了位运算的基本思想。但还处于仅仅只能明白简单题目简单程序上。

上题目:

day1的翻棋盘 

大家先思考一秒~

方法一:(来自dsy)

复杂度2^n * n;(第一个n为列,第二个n为行)

对于棋盘要么翻转要么不翻转翻转两次没有意义(这里不包括被动翻转,也就是我主动翻了一次,待会有别相邻的主动翻转而导致的又一次翻转)

所以对于第一行n(这里n == 4)就有2的n次方种翻转方法,对吧?

那么对于给定状态,我翻第一行转2^n之后,接下来的几行只有唯一一种翻转策略

    观众:“为什嘛?”

你看哈,对于第一行有2^n种翻转方法,枚举这些状态,而对于每一种状态,第一行现在已经确定的,从第二行开始(分两种情况其一全黑目标其二全白目标,我们拿全黑目标来说),对于当前需不需要翻转只需要看上一行是什么,是白色就翻转,黑色就不动,因为第一行 的 那些不符合目标的 白色旗子 只有 第二行 可以把他们改变成黑色 而 不影响第一行其他的状态,对吧?

所以最后一行根据上一行的来改变状态之后就需要检查最后一行是不是全黑就行啦。

复杂度 (2^n )* (n^2)。

这种方法很好,可我理解出来的写出来的复杂的这么差劲,如果读者前辈有什么好的想法希望指点一下小弟。

上代码:

#include<stdio.h> //进制的问题 #include<string.h> int dir[5][2] = { {0, -1}, {0, 1}, {1, 0}, {-1, 0}, {0, 0}}; char map[5][5]; char mapb[5][5]; int cmap[5]; int i, j, m, n, q; void too(int x, int y); int uff(); int main() { int con = 0; int p = 0; for(i = 0; i < 4; i ++){ scanf("%s", map[i]); strcpy(mapb[i], map[i]); } memset(cmap, 0, sizeof(cmap)); int min = 20; con = 0; for(i = 0; i < 2; i ++) for(j = 0; j < 2; j ++) for(m = 0; m < 2; m ++) for(n = 0; n < 2; n ++){ cmap[0] = i; cmap[1] = j; cmap[2] = m; cmap[3] = n; if(cmap[0] == 0){ too(0, 0); con ++; // printf(" \t %d %d\n", i, j); } if(cmap[1] == 0){ too(0, 1); con ++; // printf(" \t %d %d\n", i, j); } if(cmap[2] == 0){ too(0, 2); con ++; // printf(" \t %d %d\n", i, j); } if(cmap[3] == 0){ too(0, 3); con ++; } int qx, qy; for(qx = 1; qx < 4; qx++) //这里不应该多两个for啊.复杂度应该是2^n*m; for(qy = 0; qy < 4; qy ++){ if(map[qx - 1][qy] == 'b'){ too(qx, qy); con ++; } } if(uff()){ // printf(" \t %d %d %d %d %d\n", cmap[0], cmap[1], cmap[2], cmap[3], con); // printf("%d\n", cmap[0]); if(min > con){ p = 1; min = con; } } int t; for(t = 0; t < 4; t ++) strcpy(map[t], mapb[t]); con = 0; } memset(cmap, 0, sizeof(cmap)); int min1 = 20; int con1 = 0; for(i = 0; i < 2; i ++) for(j = 0; j < 2; j ++) for(m = 0; m < 2; m ++) for(n = 0; n < 2; n ++){ cmap[0] = i; cmap[1] = j; cmap[2] = m; cmap[3] = n; if(cmap[0] == 1){ too(0, 0); con1 ++; // printf(" \t %d %d\n", i, j); } if(cmap[1] == 1){ too(0, 1); con1 ++; } if(cmap[2] == 1){ too(0, 2); con1++; } if(cmap[3] == 1){ too(0, 3); con1 ++; } int qx, qy; for(qx = 1; qx < 4; qx ++) //这里不应该多两个for啊.复杂度应该是2^n*m; for(qy = 0; qy < 4; qy ++){ if(map[qx - 1][qy] == 'w'){ too(qx, qy); con1 ++; } } if(uff()){ // printf("%d %d %d %d %d\n", cmap[0], cmap[1], cmap[2], cmap[3], con1); if(min1 > con1){ p = 1; min1 = con1; } } int t; for(t = 0; t < 4; t ++) strcpy(map[t], mapb[t]); con1 = 0; } if(p == 1) printf("%d\n", min < min1 ? min : min1); if(p == 0) printf("Impossible\n"); return 0; } void too(int x, int y){ int i; for(i = 0; i < 5; i ++){ int dx = x + dir[i][0]; int dy = y + dir[i][1]; if(dx >= 0 && dx < 4 && dy >= 0 && dy < 4 ){ if(map[dx][dy] == 'b') map[dx][dy] = 'w'; else if(map[dx][dy] == 'w') map[dx][dy] = 'b'; } else ; } return; } int uff(){ int i, j; for(i = 0; i < 4; i ++) for(j = 0; j < 4; j ++){ if(map[i][j] != map[0][0]){ return 0; } } return 1; }

方法二:

 就是dfs,复杂度2^16。

思路:

   首先我要知道dfs函数怎么写,一切都好办(废话)。

还是最多全翻转,就是16种情况(都不翻,只翻一个,两个,......,16个都翻转)。for一下这16种情况,接下来就是把  对于限定好了的你只能翻转多少个旗子的这个数目  作为递归出口,还有就是找到了结果也要出来,没出界也没用完  限定好了的你只能翻转多少个旗子的这个数目  就继续在里面递归。

相当于   从16个里面   挑出来   这个几个限定的可翻转的个数   有多少种情况,组合数学~。C几几...,搜索到最深处没有就返回,注意别像我似的傻了吧唧的memset,或者是strcpy,直接就再变换一下就行,写一个change函数。

写的不是很明白,见谅。

哪里不懂欢迎评论区留言。

上代码:

#include<iostream> #include<cstring> #include<cstdio> int flag; char map[5][5]; int step; int dir[5][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1}, {0, 0} }; int change(int ttx, int tty) { for(int i = 0; i < 5; i ++) { int dx = ttx + dir[i][0] ; int dy = tty + dir[i][1] ; if(dx >= 0 && dx < 4 && dy >= 0 && dy < 4) { map[dx][dy] = !map[dx][dy]; } } } int ifall() { for(int i = 0; i < 4; i ++) { for(int j = 0; j < 4; j ++) { if(map[i][j] != map[0][0]) { return 0; } } } return 1; } void dfs(int x, int y, int deep) { if(deep == step) { flag = ifall(); // printf("%d ", flag); return; } if(flag == 1 || x == 4) return; change(x, y); if(y < 3) dfs(x, y + 1, deep + 1); else dfs(x + 1, 0, deep + 1); change(x, y); if(y < 3) dfs(x, y + 1, deep); else dfs(x + 1, 0, deep); return; } int main() { char temp[7][7]; flag = 0; // freopen("in.txt", "r", stdin); memset(map, false, sizeof(map)); for(int i = 0; i < 4; i ++) { scanf("%s", &temp[i]); for(int j = 0; j < 4; j ++) { if(temp[i][j] == 'b') map[i][j] = true; } } for( step = 0; step <= 16; step ++) { dfs(0,0,0); if(flag) break; } if(flag) printf("%d\n", step); else printf("Impossible\n"); // change(1, 1); // for(int i = 0; i < 4; i ++){ // for(int j = 0; j < 4; j ++) // { // printf("%d ", map[i][j]); // } // printf("\n"); // } }

参考博客

方法三:

 

集训的时候总是遇见位运算的奇技淫巧,决定死磕这个位运算的部分,以后一定会用上。

看懂啦,总算可以用位运算写啦。

看了Matrix67的位运算专题发现这里不是太懂

/*

1.      1314520 xor 19880516 = 20665500

2.      === 4. not运算 ===

3.    最大公约数的二进制算法用除以2操作来代替慢得出奇的mod运算,效率可以提高60%。

4.   位运算的简单应用     有时我们的程序需要一个规模不大的Hash表来记录状态。比如,做数独时我们需要27个Hash表来统计每一行、每一列和每一个小九宫格里已经有哪些数了。此时,我们可以用27个小于2^9的整数进行记录。例如,一个只填了2和5的小九宫格就用数字18表示(二进制为000010010),而某一行的状态为511则表示这一行已经填满。需要改变状态时我们不需要把这个数转成二进制修改后再转回去,而是直接进行位操作。在搜索时,把状态表示成整数可以更好地进行判重等操作。这道题是在搜索中使用位运算加速的经典例子。以后我们会看到更多的例子。

5.   这道题那道题啊

*/

过后来补。。

......

*********************************************华丽的分割线****************************************************

续接方法三

他的位运算太高级了,看不懂,不过仅仅看了第一篇就感觉思路有了不少提升,现在 这道题(点击跳转)  最好玩的位运算bit压缩+bfs方法来啦!!!

PS:用进制转换工具会更加方便清晰的了解 位运算压缩状态来优化这类问题 的一个基本思路。

自己研究出来奥妙无穷,这里用到的是  广搜bfs + 队列 + 位运算

哪里不懂欢迎评论留言

上代码:

#include<cstdio> #include<cstring> #include<iostream> using namespace std; #include<queue> int dir[4][4] = { {0, -1}, {0, 1}, {1, 0}, {-1, 0} }; char map[5][5]; int change[20]; int temp; int t = 0; int visit[65535]; int inmap(int x, int y) { if(x >= 0 && x < 4 && y >= 0 && y < 4) return 1; else return 0; } int state = 0; struct Node { int step; int states; };//p[20]; int bfs(int temp) { queue<Node>p; Node cur, next; cur.step = 0; cur.states = temp; p.push(cur); while(!p.empty()) { cur = p.front(); p.pop(); if(cur.states == 0 || cur.states == 65535) return cur.step; for(int i = 0; i < 16; i ++) { next.step = cur.step + 1; next.states = cur.states ^ change[i]; // printf("%d+ \n", next.states); if(next.states == 0 || next.states == 65535) return next.step; if(visit[next.states]) continue; p.push(next); visit[next.states] = 1; } } return -1; // printf("Impossible\n"); } int main() { // freopen("in.txt", "r", stdin); memset(visit, 0, sizeof(visit)); for(int i = 0; i < 4; i ++) scanf("%s", map[i]); temp = 0; for(int i = 0; i < 4; i ++) //初始化状态 for(int j = 0; j < 4; j ++) { temp <<= 1; if(map[i][j] == 'b') { temp += 1; } } for(int i = 0; i < 4; i ++) //对change赋值 for(int j = 0; j < 4; j ++) { state = 0; state ^= (1 << (15 -( i * 4 + j))); for(int k = 0; k < 4; k ++) { int dx = i + dir[k][0]; int dy = j + dir[k][1]; if(inmap(dx, dy)) { state ^= (1 << (15 -( dx * 4 + dy))); } else continue; } change[t ++] = state; } int ans = bfs(temp); if(ans == -1) printf("Impossible\n"); else printf("%d\n", ans); }

这道题A掉后发现不会算复杂度,求大佬解答留言。如果有给小弟指点一二的小弟感激不尽(抱拳),真的是不会算这个复杂度。

 

一些丑事&心里话:

http://tool.chinaz.com/Tools/textencrypt.aspx

U2FsdGVkX18zm6lStCvPlw3VI4Ai6QDcbOEuPxN8Z27+3Zk7eSKEnHRVvbvcF6BF JqgEI/E2h8QkGjVNqs9WWMbYePoSwt3fFMWHNrDGQbfhXPpAgMEFziIWtTKCmdKC KDRKHqZi6WPFMF3g0TW4Nfmd9Flg2ttsQAJyOanOqExaTd7LhxhBkD1BoIESrap0 5kLWt1KNN2CGqPxoi+PydpnLdvlvQ2/QYu6Gr6rcvwaj/8JKXd3YUCrSi54isjLF 9gzODTGldCKdt9zHoj9AHeuQowvln68DfWzO3JzoiCjF5S+vah2FCuE47wLUtDKF DNYqLew02lIzlRg6ilHx3fIWUr/rXNUHT/paupPIVqVvUaReetafQodk6WlakJ7d L7N8eKpu0tbTEigMUcdO3mtmHwQFkyU6dfb3tBFo8Pgx97TAPbsoW2JmRphkk07g 1T0KM7Sme1D0xe6Wl6ythrpmGibA9C5AXXFH7qSfe6sGk8Ecx7n97rMe2M5LBBou ELdmkeyaFFXgJngVg18Cl5f1EWAnzAHATs9PIut3IZ2Te/vTTNT8bxT2V8ksnurF ziLUHYGmh8utxGTaEdvNTwq8x3p8gFiKz7drzND9dEcHZq1B0ETQH2ogYQOndjt9 6DT4FdcoUtSRhnr6ZaJkw4AbdA28BbnK3XistOCEstLww1SIIUJB6RXaIcoi1D/q VvHYQmJifqGsruwJJgh+MXh35If6wsSAZu8CJcYxuIV/mnNHyTp+frPspWnfapcI 6If+5RhagEyBCR/V2heogo8Rm7o/yeg/u11+X5EBscmMEvORCdzJF7F9YKMaBRTi eUW2Ywwge8AN7SfxfMh85qa3u/IZFZMoAOGS+a2bQ7k=

转载请注明原文地址: https://www.6miu.com/read-10917.html

最新回复(0)