1.题意:有一组3*3的只有时针的挂钟阵列,每个时钟只有0,3,6,9三种状态;对时针阵列有9种操作,每种操作只对特点的几个时钟拨一次针,即将时针顺时针波动90度,现在试求从初试状态到阵列全部指向0的状态所需要的最小操作数的操作方案;
2.输入输出:输入给出阵列初始状态,0,1,2,3分别表示0,3,6,9;要求输出最快方案的操作序列;
3.分析:IOI 1994的考题,BFS是比较容易想到的方法之一,关键是如何简洁的表示和改变BFS过程中的阵列状态;这里使用位运算的方法;具体如下:
首先一共9个时钟,每个时钟有0,1,2,3这4种状态,所以每个时钟可以由两位二进制数表示,则整个阵列就可以用18位二进制数表示,这里表示的问题就解决了;下一步就是如何表示"转动90度了",这里分三步,1)从状态中取出要调整的那个钟,2)对取出的sub部分进行模拟转动90度的操作,3)将处理完的sub放回原状态;经过这样的三个步骤之后,对一个钟的修改就完成了,针对每种操作,对这个操作内的每个时钟进行修改,就对当前状态完成了操作;
综上所述,这样的位运算方法使得表示更加简洁,计算的更快;具体代码如下:
1 # include <cstdio> 2 # include <iostream> 3 # include <queue> 4 # include <cstring> 5 # include <stack> 6 using namespace std; 7 const int MAXN=1<<18; 8 int clock[10]; 9 int vis[MAXN]; 10 struct Node 11 { 12 int status,move,pre; 13 Node (){} 14 Node(int ss,int mm,int pp) 15 { 16 status=ss; 17 move=mm; 18 pre=pp; 19 } 20 }L[MAXN]; 21 int M[9][5]= 22 {{0,1,3,4,-1},{0,1,2,-1,-1},{1,2,4,5,-1}, 23 {0,3,6,-1,-1},{1,3,4,5,7},{2,5,8,-1,-1}, 24 {3,4,6,7,-1},{6,7,8,-1,-1},{4,5,7,8,-1}}; 25 void Init() 26 { 27 for(int i=0;i<9;i++) 28 scanf("%d",&clock[i]); 29 memset(vis,0,sizeof(vis)); 30 } 31 void Solve() 32 { 33 queue<Node> Q; 34 int start=0; 35 int len=0; 36 for(int i=0;i<9;i++) 37 start|=(clock[i]<<(i<<1)); 38 Q.push(Node(start,-1,-1)); 39 vis[start]=1; 40 while(!Q.empty()) 41 { 42 Node temp=Q.front(); 43 Q.pop(); 44 L[len++]=temp; 45 if(temp.status==0) 46 break; 47 //printf("%d ",temp.move); 48 for(int i=0;i<9;i++) 49 { 50 int nclock=0; 51 int nstatus=temp.status; 52 for(int j=0;j<5;j++) 53 if(M[i][j]>=0) 54 { 55 nstatus&= ((1<<18)-1) ^ (3<<(2*(M[i][j]))); 56 //"1111[目标位]1111",把待处理部分位的位置清零 57 nclock=(temp.status >> (2*(M[i][j]))) & 3; 58 //取出要处理的部分位 59 nstatus|=((nclock+1) & 3) << (2*(M[i][j])); 60 //填入处理后的部分位 61 } 62 if(!vis[nstatus]) 63 { 64 Q.push(Node(nstatus,i,len-1)); 65 vis[nstatus]=1; 66 } 67 } 68 } 69 len--; 70 stack<int> S; 71 while(1) 72 { 73 S.push(L[len].move+1); 74 len=L[len].pre; 75 if(L[len].pre<0) 76 break; 77 } 78 printf("%d",S.top()); 79 S.pop(); 80 while(!S.empty()) 81 { 82 int t=S.top(); 83 S.pop(); 84 printf(" %d",t); 85 } 86 printf(" "); 87 } 88 int main() 89 { 90 //freopen("in.txt","r",stdin); 91 //freopen("out.txt","w",stdout); 92 Init(); 93 Solve(); 94 return 0; 95 }
eg.位运算举例
1.两位二进制数表示一个状态,4个一组,即8位二进制数表示一组状态
2.要实现的操作,对第i+1个状态进行加一操作
3.大体思路:1)待处理状态num做一个副本n; 2)num把要处理掉的那一位挖空 ;3)把待处理单位sub从n取出;
4)对sub操作 ;5)把sub塞回去
1 void Init() 2 { 3 int num=0; 4 for(int i=0;i<4;i++) 5 num|=(Num[i]<<(i*2)); 6 } 7 void Change(int i) 8 { 9 int n=num; 10 num&=((1<<8)-1)^(3<<(i*2)); 11 //把要处理的那一位挖空为什么是3呢,因为二进制"11"为3,正好把两个二进制位"挖空" 12 int sub=(n>>(i*2))&3;//这样除了最低的两位其他的都会与0做&运算,都扑街了 13 sub+=1; 14 num|=(sub<<(2*i)); 15 }