*注:本文对状态压缩的描述非正式化,比较随意,意在让人容易理解,下面开始谈谈我对状态压缩的理解。
1.为什么要采用状态压缩?
采用状态压缩的主要原因是原状态不容易表示或者状态数目过多,内存不够用。
2.用状态压缩有什么好处?
当然自然解决了上面的两个问题-----状态容易表达,至于内存,用一个数的二进制表示状态可以节省很多内存空间(当然也有使用的局限性)
3.状态压缩的难点?
状压一般是用于状压BFS和状压DP,状压的主要难点就是怎么压缩状态,然后就是位运算的使用,位运算一定要熟练。下面介绍位运算
& ---- 按位与,可以将某个数的某二进制位置为0,也可以用于取出某个二进制位
| ---- 按位或,可以将某个数的某二进制位置为1.
~ ---- 非,将一个数的所有二进制位取反
^ ---- 异或,相同为0,不同为1
本文将从几个例题出发,讲解状压的方法及原理
以下例题题意不在描述,还请先点开链接读题
例题一:
HDU 1429 胜利大逃亡(续)
一看此题,就是BFS搜索,怎么搜呢?考虑到钥匙最多只有10把,很容易想到状态压缩,将每把钥匙对应一个二进制数的一位,这样状态就可以轻松表示了
那么为什么要状态压缩?原因很简单,因为到达任意一个点(z,y),Ignatius身上带的钥匙的种类数量都可能不同,应该是属于不同的状态.想到这点程序就不难写了
下面给出AC代码,希望先自己写,不会了再参考下
/*
author: tpbluesky
time: 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");
else
printf("%d\n",ans);
}
return 0;
}
练习:
HDU 5094 --- Maze
HDU 1044 --- Collect More Jewels
例题二:
POJ 1324 Holedox Moving
刚看完题目,可能会感觉无从下手,状态是蛇所在位子的每个坐标,这种状态实在不容易表示,坐标太多,空间不够,怎么办,这时我们也很容易想到二进制表示
状态,但仍然不是很方便表示,经过观察,我们发现每个点相对前个点的相对方向其实很容易表示的,用二位二进制位正好表示四个方向,这样我们很容易从蛇头推出
每个蛇身的坐标,转移其实比较特别,直接移位就行了,不清楚的自己在纸上画画看,想到这点已经程序就不是很难写了,但是实现的时候需要一定技巧,并且能很熟练使 用位运算
下面还是给出AC代码,代码附带注释
/*
author: tpbluesky
time: 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待续。。。。。