状态压缩讲解

来源:互联网 发布:php验证码源码 编辑:程序博客网 时间:2024/09/21 08:56


*注:本文对状态压缩的描述非正式化,比较随意,意在让人容易理解,下面开始谈谈我对状态压缩的理解。


1.为什么要采用状态压缩?
采用状态压缩的主要原因是原状态不容易表示或者状态数目过多,内存不够用。


2.用状态压缩有什么好处?
当然自然解决了上面的两个问题-----状态容易表达,至于内存,用一个数的二进制表示状态可以节省很多内存空间(当然也有使用的局限性)

3.状态压缩的难点?
状压一般是用于状压BFS和状压DP,状压的主要难点就是怎么压缩状态,然后就是位运算的使用,位运算一定要熟练。下面介绍位运算
& ---- 按位与,可以将某个数的某二进制位置为0,也可以用于取出某个二进制位
| ---- 按位或,可以将某个数的某二进制位置为1.
~ ---- 非,将一个数的所有二进制位取反
^ ---- 异或,相同为0,不同为1


本文将从几个例题出发,讲解状压的方法及原理


以下例题题意不在描述,还请先点开链接读题
例题一:
HDU 1429  胜利大逃亡(续)
一看此题,就是BFS搜索,怎么搜呢?考虑到钥匙最多只有10把,很容易想到状态压缩,将每把钥匙对应一个二进制数的一位,这样状态就可以轻松表示了
那么为什么要状态压缩?原因很简单,因为到达任意一个点(z,y),Ignatius身上带的钥匙的种类数量都可能不同,应该是属于不同的状态.想到这点程序就不难写了
下面给出AC代码,希望先自己写,不会了再参考下
/*author: tpblueskytime:2015年8月16日14:04:48   题解:状态压缩 */#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>#include <string>#include <vector>#include <set>#include <map>#include <queue>#include <string>#include <sstream>#define inf 0x3f3f3f3f#define eps 1e-8#define sqr(x) ((x)*(x))using namespace std;typedef long long ll;const int maxn = 22;char mp[maxn][maxn];int vis[maxn][maxn][1<<10], n, m , t; struct node{int x, y, step, state; }st;int dx[] = {1,-1,0,0};int dy[] = {0,0,1,-1};bool isok(int x,int y){if(x < 1 || y < 1 || x > n ||  y > m || mp[x][y] == '*')return false;return true;}int bfs(){queue<node> q;memset(vis,0,sizeof(vis));vis[st.x][st.y][0] = 1;q.push(st);while(!q.empty()){node tp = q.front();q.pop();if(mp[tp.x][tp.y] == '^')return tp.step;//cout<<"              "<<tp.x<<" "<<tp.y<<" "<<tp.step<<" "<<tp.state<<endl;for(int i = 0;i < 4;++i){node temp;temp.x = tp.x + dx[i], temp.y = tp.y+dy[i], temp.step = tp.step+1, temp.state = tp.state;if(isok(temp.x,temp.y) && !vis[temp.x][temp.y][temp.state]){vis[temp.x][temp.y][temp.state] = 1;if(mp[temp.x][temp.y] >= 'A' && mp[temp.x][temp.y] <= 'J'){int t = mp[temp.x][temp.y] - 'A';if(temp.state&(1<<t))q.push(temp);}else if(mp[temp.x][temp.y] >= 'a' && mp[temp.x][temp.y] <= 'j'){vis[temp.x][temp.y][temp.state]= 0;int t = mp[temp.x][temp.y] - 'a';temp.state |= (1<<t);if(!vis[temp.x][temp.y][temp.state]){vis[temp.x][temp.y][temp.state] = 1;q.push(temp);}}else{q.push(temp);}}} }return -1;}int main(){while(scanf("%d%d%d",&n,&m,&t) == 3){for(int i = 1;i <=n;++i){scanf("%s",mp[i]+1);for(int j = 1;j <= m;++j){if(mp[i][j] == '@')st.x = i, st.y = j, st.state = 0;}}int ans = bfs();if(ans == -1 || ans >= t)printf("-1\n");elseprintf("%d\n",ans);}    return 0;}


练习:
HDU 5094 --- Maze
HDU 1044 --- Collect More Jewels


例题二:
POJ 1324 Holedox Moving
刚看完题目,可能会感觉无从下手,状态是蛇所在位子的每个坐标,这种状态实在不容易表示,坐标太多,空间不够,怎么办,这时我们也很容易想到二进制表示
状态,但仍然不是很方便表示,经过观察,我们发现每个点相对前个点的相对方向其实很容易表示的,用二位二进制位正好表示四个方向,这样我们很容易从蛇头推出
每个蛇身的坐标,转移其实比较特别,直接移位就行了,不清楚的自己在纸上画画看,想到这点已经程序就不是很难写了,但是实现的时候需要一定技巧,并且能很熟练使用位运算

下面还是给出AC代码,代码附带注释

/*author: tpblueskytime:   2015年8月16日21:57:40题解:<span style="white-space:pre"></span>状压BFS*/#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>#include <string>#include <vector>#include <set>#include <map>#include <queue>#include <string>#include <sstream>#define inf 0x3f3f3f3f#define eps 1e-8#define sqr(x) ((x)*(x))using namespace std;typedef long long ll;const int maxn = 22;int vis[maxn][maxn][1<<14];//标记状态 int mp[maxn][maxn];int n, m, k;int lor[] = {0,1,2,3};//up,down,left,right 此处的方向和下面数组的方向是一致的,便于操作 int dx[] = {1,-1,0,0};int dy[] = {0,0,1,-1};struct node{int x, y, state, step;}st;int pos[10][2];bool isok(int x,int y,int orx, int ory, int s){if(x < 1 || y < 1|| x > n || y > m || mp[x][y] == 1)return false;int t = 3;//cout<<"       "<<s<<endl;for(int i = 0;i < k;++i)//此处是还原原来状态的每个点,判断拓展的点是否是蛇身 {int p = (s>>(i*2))&t; //将s的第i*2+1,i*2+2移到末尾,& 011,得到这两位的数,就可以推出方向 if(p == 0)orx -= 1;if(p == 1)orx += 1;if(p == 2)  ory -= 1;if(p == 3)  ory += 1;//cout<<"        "<<p<<" "<<orx<<" "<<ory<<endl;if(x == orx && y == ory)//点重复,走到了蛇身 return false;}return true;}int bfs(){memset(vis,0,sizeof(vis));queue<node> q;q.push(st);vis[st.x][st.y][st.state] = 1;int res = -1;while(!q.empty()){node tp = q.front();q.pop();if(tp.x == 1 && tp.y == 1){res = tp.step;break;}for(int i = 0;i < 4;++i){node temp;temp.x = tp.x + dx[i], temp.y = tp.y+dy[i], temp.step = tp.step+1, temp.state = (tp.state<<2)|lor[i];  int t = (1<<(2*k-2))-1; temp.state &= t;//上面的右移比较巧妙,可以自己动手模拟一下看看,此处是将temp.state高位全部置为0 //cout<<temp.state<<endl;if(isok(temp.x, temp.y, tp.x, tp.y, tp.state) && !vis[temp.x][temp.y][temp.state]){vis[temp.x][temp.y][temp.state] = 1;q.push(temp);}}}return res;}int main(){int cas = 1;while(scanf("%d%d%d",&n,&m,&k) == 3){memset(mp,0,sizeof(mp));if(!n && !m && !k)break;for(int i = 0;i < k;++i){scanf("%d%d",&pos[i][0],&pos[i][1]);if(i == 0){st.x = pos[i][0], st.y = pos[i][1],st.state = 0, st.step = 0;}}int p;scanf("%d",&p);for(int i = 0, a, b;i < p;++i){scanf("%d%d",&a,&b);mp[a][b] = 1;}for(int i = 1;i < k;++i)//获取初始状态,不懂得可以手动模拟下 {int s = 0;int tx = pos[i][0]-pos[i-1][0] , ty = pos[i][1]-pos[i-1][1];if(tx == -1) s = (s|lor[0])<<(i*2-2); if(tx == 1) s = (s|lor[1])<<(i*2-2);if(ty == -1)s = (s|lor[2])<<(i*2-2);if(ty == 1) s = (s|lor[3])<<(i*2-2);st.state |= s;}//cout<<st.state<<endl;printf("Case %d: %d\n",cas++,bfs());}    return 0;}



练习:
POJ 1184 --- 聪明的打字员
HDU 1043 --- Eight


看完状压BFS,状压DP待续。。。。。

0 0
原创粉丝点击