前言
有些问题,时刻需要知道问题的当前状态,所以需要的每一个状态进行保存,而在计算机中,简洁快速的二进制受到大家表示状态时的青睐,最近在学习中总结了一下状态压缩,它不仅应用于我们常提的DP,在其他算法解决问题是也能起到不同凡响的作用。
正文
在一个需要描述的对象有两种不同的状态时,通常采用二进制数来表示,0,1分别表示两种不同的状态,这就做到了对问题的足够抽象,使下面的操作更为简洁。
空谈说不出来什么,下面直接进入实战(不懂位运算的先补一下)。
问题一:TSP 经典问题
一个N个点的带全有向图,求一条路径,进过该图上的各个点一次且仅一次,并且路径上的边权值之和最小(或最大)。
※n<=16.<---------------------这一般是状态压缩的标志
分析:由于只有16个点,所以可以用一个整数来表示点集。
例如:
5=0000000000000101 它的第0位和第2为位是1,就表示这个点集中有0,2这两个点。
所以一个整数就可以完美的表示一个点集,它显然也能表示是第几个点。
下面回到题目:用f[I][j]表示当前走到第i个点,经过点集j中所有的点恰好一次的路径权值最小和,如果不存在,则为+oo。
对于单点集,即j中仅包含i的情况,初始化f[i][j]=0,
状态转移:f[i][j]=min{f[s][k]+dist[s][j]}
其中s表示一个与i直接连通的点,k则表示j集合中去掉i后的状态,dist[s][j]表示边的权值。
最终答案:f[node][(1<<n)-1],其中node为任意点。
在程序实现中,需要一点位运算的基础,如从集合j中去掉k点的操作为:
J and(not (1<<k)).
比较简单,代码就省了。
问题二:软件补丁问题(补丁,错误) vijos1019
题目大意:一个软件包含了n个错误,现在公司发布了m个软件,每个软件的运用条件是必须包含某几个特定错误而必须不含某几个特定错误,而补丁的作用是,能修复某几个特定错误,又会带来某几个特定错误,每个补丁都有一定的运行时间,现在求最少用多少时间,把软件修复到没有错误,如果无法修复,输出-1。
数据范围:n<=20 m<=100.
分析:根据题目数据范围,可用二进制串表示当前软件的错误情况,某一位为1,表示含有这个错误,否则没有,由于二进制数有第0位,所以第(i-1)位表示第i位情况。接下来就有了显然的东西:把各个状态之间根据补丁运用关系连边,做一遍最短路即可。注意实践时边扩张状态边最短路,效率很高。
细节:判断一个状态i是否包含另一个状态j:I or j=I 成立则i包含j。
判断一个状态i不含另一个状态j :(not i) or j= not i成立则i不含j.
给一个状态i增加j中的错误(有可能重合):i=I or j.
给一个状态i减少j中的错误:I and (not j)
代码实现:
1 //软件补丁问题 2 program error(input,output); 3 const 4 queuelength = 2000000; 5 maxm = 100; 6 maxstate = 1050000; 7 var 8 d : array[0..maxstate] of longint; 9 v : array[0..maxstate] of boolean; 10 f1,f2,b1,b2 : array[0..maxm] of longint; 11 time : array[0..maxm] of longint; 12 q : array[0..queuelength] of longint; 13 n,m : longint; 14 procedure init; 15 var 16 ch : char; 17 i,j : longint; 18 begin 19 readln(n,m); 20 fillchar(f1,sizeof(f1),0); 21 fillchar(f2,sizeof(f2),0); 22 fillchar(b1,sizeof(b1),0); 23 fillchar(b2,sizeof(b2),0); 24 fillchar(time,sizeof(time),0); 25 for i:=1 to m do 26 begin 27 read(time[i]); 28 read(ch); 29 for j:=1 to n do 30 begin 31 read(ch); 32 case ch of 33 '0' : continue; 34 '+' : inc(b1[i],1<<(j-1)); 35 '-' : inc(b2[i],1<<(j-1)); 36 end; { case } 37 end; 38 read(ch); 39 for j:=1 to n do 40 begin 41 read(ch); 42 case ch of 43 '0' : continue; 44 '+' : inc(f1[i],1<<(j-1)); 45 '-' : inc(f2[i],1<<(j-1)); 46 end; { case } 47 end; 48 readln; 49 end; 50 end; 51 procedure build_and_getanswer; 52 var 53 head,tail,now,i : longint; 54 newstate : longint; 55 begin 56 fillchar(d,sizeof(d),63); 57 fillchar(v,sizeof(v),false); 58 d[(1<<n)-1]:=0; 59 v[(1<<n)-1]:=true; 60 head:=0; 61 tail:=1; 62 q[1]:=(1<<n)-1; 63 while head<tail do 64 begin 65 inc(head); 66 now:=q[head]; 67 v[now]:=false; 68 for i:=1 to m do 69 if ((b1[i] or now)=now) then 70 if ((b2[i] or (not now))=(not now)) then 71 begin 72 newstate:=(now and (not f2[i])); 73 newstate:=(newstate or f1[i]); 74 if d[now]+time[i]<d[newstate] then 75 begin 76 d[newstate]:=d[now]+time[i]; 77 if not v[newstate] then 78 begin 79 inc(tail); 80 q[tail]:=newstate; 81 v[newstate]:=true; 82 end; 83 end; 84 end; 85 end; 86 end; { build } 87 procedure print; 88 begin 89 if d[0]>19950714 then 90 writeln(0) 91 else 92 writeln(d[0]); 93 end; { print } 94 begin 95 assign(input,'error.in');reset(input); 96 assign(output,'error.out');rewrite(output); 97 init; 98 build_and_getanswer; 99 print; 100 close(input); 101 close(output); 102 end.
问题三:拯救大兵瑞恩(CTSC1999)
题目大意:在一个N*M的迷宫中,要从左上走到右下,每次只能向右或向下一步,走的过程中可能有路不通,或者有要钥匙的门,而钥匙又分布在迷宫中的各个角落,求最少步数。
数据范围:m,n<=15 钥匙数p<=10
分析:又是比较明显的状态压缩,压得显然是钥匙,这里甚至只用判断是否有钥匙,位运算极为简练,只需要BFS,第一次到(n,m)一定是最优解,实现时用v[x,y,k]判重,表示在(x,y)状态为k,是否访问过,can[x1,y1,x2,y2]表示能否从(x1,y1)到(x2,y2),
Key[x1,y1]表示(x1,y1)点的钥匙状态,door[x1,y1,x2,y2]表示(x1,y1)与(x2,y2)间是否有门。
细节:1.要判断状态合理之后再判断是否以到达(n,m)。
2.要注意key数组记录已经压缩好的状态,因为一个地方有有多把钥匙的情况,如果仅标记而在搜索过程中压缩的话,会丢掉钥匙。
代码实现:
1 //拯救大兵瑞恩 2 program help(input,output); 3 type 4 node = record 5 x,y,state,dist : integer; 6 end; 7 var 8 can : array[0..16,0..16,0..16,0..16] of boolean; 9 key : array[0..16,0..16] of integer; 10 door : array[0..16,0..16,0..16,0..16] of integer; 11 v : array[0..16,0..16,0..1024] of boolean; 12 fx : array[1..4] of longint=(0,1,0,-1); 13 fy : array[1..4] of longint=(1,0,-1,0); 14 q : array[0..200000] of node; 15 n,m,sumkey,ans : longint; 16 procedure init; 17 var 18 i,g1,x1,y1,x2,y2,k : longint; 19 begin 20 fillchar(can,sizeof(can),true); 21 fillchar(door,sizeof(door),0); 22 fillchar(key,sizeof(key),0); 23 readln(n,m,sumkey); 24 readln(k); 25 for i:=1 to k do 26 begin 27 read(x1,y1,x2,y2,g1); 28 if g1=0 then 29 begin 30 can[x1,y1,x2,y2]:=false; 31 can[x2,y2,x1,y1]:=false; 32 continue; 33 end; 34 door[x1,y1,x2,y2]:=g1; 35 door[x2,y2,x1,y1]:=g1; 36 can[x1,y1,x2,y2]:=false; 37 can[x2,y2,x1,y1]:=false; 38 end; 39 readln(k); 40 for i:=1 to k do 41 begin 42 readln(x1,y1,g1); 43 key[x1,y1]:=key[x1,y1] or (1<<(g1-1)); 44 end; 45 end; { init } 46 procedure bfs(); 47 var 48 head,tail,i : longint; 49 newx,newy,newstate : longint; 50 begin 51 head:=0; 52 tail:=1; 53 fillchar(v,sizeof(v),false); 54 q[1].x:=1; 55 q[1].y:=1; 56 q[1].dist:=0; 57 q[1].state:=0; 58 v[1,1,0]:=true; 59 while head<tail do 60 begin 61 inc(head); 62 for i:=1 to 4 do 63 begin 64 newx:=q[head].x+fx[i]; 65 newy:=q[head].y+fy[i]; 66 if (newx>n)or(newx<1)or(newy<1)or(newy>m) then 67 continue; 68 if can[q[head].x,q[head].y,newx,newy] then 69 if not v[newx,newy,q[head].state] then 70 begin 71 inc(tail); 72 q[tail].x:=newx; 73 q[tail].y:=newy; 74 q[tail].state:=q[head].state; 75 q[tail].dist:=q[head].dist+1; 76 v[newx,newy,q[head].state]:=true; 77 if (newx=n)and(newy=m) then 78 begin 79 ans:=q[tail].dist; 80 exit; 81 end; 82 end; 83 if (door[q[head].x,q[head].y,newx,newy]>0) then 84 if ((q[head].state and (1<<(door[q[head].x,q[head].y,newx,newy]-1)))>0) then 85 if not v[newx,newy,q[head].state] then 86 begin 87 inc(tail); 88 q[tail].x:=newx; 89 q[tail].y:=newy; 90 q[tail].dist:=q[head].dist+1; 91 q[tail].state:=q[head].state; 92 v[newx,newy,q[head].state]:=true; 93 if (newx=n)and(newy=m) then 94 begin 95 ans:=q[tail].dist; 96 exit; 97 end; 98 end; 99 if (key[newx,newy]>0)and((can[q[head].x,q[head].y,newx,newy])) then 100 begin 101 newstate:=q[head].state or key[newx,newy]; 102 if not v[newx,newy,newstate] then 103 begin 104 inc(tail); 105 q[tail].x:=newx; 106 q[tail].y:=newy; 107 q[tail].dist:=q[head].dist+1; 108 q[tail].state:=newstate; 109 v[newx,newy,newstate]:=true; 110 if (newx=n)and(newy=m) then 111 begin 112 ans:=q[tail].dist; 113 exit; 114 end; 115 end; 116 end; 117 if (key[newx,newy]>0)and(door[q[head].x,q[head].y,newx,newy]>0)and((q[head].state and(1<<(door[q[head].x,q[head].y,newx,newy]-1)))>0) then 118 begin 119 newstate:=q[head].state or key[newx,newy]; 120 if not v[newx,newy,newstate] then 121 begin 122 inc(tail); 123 q[tail].x:=newx; 124 q[tail].y:=newy; 125 q[tail].dist:=q[head].dist+1; 126 q[tail].state:=newstate; 127 v[newx,newy,newstate]:=true; 128 if (newx=n)and(newy=m) then 129 begin 130 ans:=q[tail].dist; 131 exit; 132 end; 133 end; 134 end; 135 end; 136 end; 137 end; { bfs } 138 procedure print; 139 begin 140 if ans=0 then 141 writeln(-1) 142 else 143 writeln(ans); 144 end; { print } 145 begin 146 init; 147 bfs(); 148 print; 149 end.
最后个大家留下两道题目来思考:
1. 纠结迷宫:http://hzoi.openjudge.cn/never/1007/
2. 毒药?解药?:http://hzoi.openjudge.cn/never/1008/
这里是以上两个题的程序,实在过不去再参考吧,比较明了。
1 //纠结迷宫 2 program maze(input,output); 3 type 4 node = record 5 x,y,state,dist : longint; 6 end; 7 var 8 q : array[0..4000000] of node; 9 fx : array[1..4] of longint=(0,1,0,-1); 10 fy : array[1..4] of longint=(1,0,-1,0); 11 can : array[0..51,0..51] of boolean; 12 map : array[0..51,0..51] of longint; 13 v : array[0..51,0..51,0..65536] of boolean; 14 n,m,ans : longint; 15 procedure bfs(); 16 var 17 head,tail : longint; 18 i : longint; 19 newx,newy,newstate : longint; 20 begin 21 head:=0; 22 tail:=1; 23 q[1].x:=1; 24 q[1].y:=1; 25 q[1].state:=0; 26 q[1].dist:=0; 27 fillchar(v,sizeof(v),false); 28 v[1,1,0]:=true; 29 while head<tail do 30 begin 31 inc(head); 32 for i:=1 to 4 do 33 begin 34 newx:=q[head].x+fx[i]; 35 newy:=q[head].y+fy[i]; 36 if (newx=n)and(newy=n) then 37 begin 38 ans:=q[head].dist+1; 39 exit; 40 end; 41 if (map[newx,newy]>=1)and(map[newx,newy]<=m)and(can[newx,newy]) then 42 begin 43 newstate:=q[head].state or (1<<(map[newx,newy]-1)); 44 if not v[newx,newy,newstate] then 45 begin 46 inc(tail); 47 q[tail].x:=newx; 48 q[tail].y:=newy; 49 q[tail].state:=newstate; 50 q[tail].dist:=q[head].dist+1; 51 v[newx,newy,newstate]:=true; 52 end; 53 end; 54 if can[newx,newy] then 55 if not v[newx,newy,q[head].state] then 56 begin 57 inc(tail); 58 q[tail].x:=newx; 59 q[tail].y:=newy; 60 q[tail].state:=q[head].state; 61 q[tail].dist:=q[head].dist+1; 62 v[newx,newy,q[head].state]:=true; 63 continue; 64 end; 65 if (map[newx,newy]>m) then 66 begin 67 if ((1<<(map[newx,newy]-m-1))and(q[head].state))=(1<<(map[newx,newy]-m-1)) then 68 if not v[newx,newy,q[head].state] then 69 begin 70 inc(tail); 71 q[tail].x:=newx; 72 q[tail].y:=newy; 73 q[tail].state:=q[head].state; 74 q[tail].dist:=q[head].dist+1; 75 v[newx,newy,q[head].state]:=true; 76 continue; 77 end; 78 end; 79 end; 80 end; 81 end; { bfs } 82 procedure init; 83 var 84 i,j,k : longint; 85 begin 86 readln(n,m); 87 for i:=1 to n do 88 for j:=1 to n do 89 read(map[i,j]); 90 fillchar(can,sizeof(can),false); 91 for i:=1 to n do 92 for j:=1 to n do 93 if (map[i,j]=0)or((map[i,j]>=1)and(map[i,j]<=m)) then 94 can[i,j]:=true; 95 for i:=1 to n do 96 for j:=1 to n do 97 if map[i,j]=-2 then 98 begin 99 can[i,j]:=false; 100 for k:=1 to 4 do 101 can[i+fx[k],j+fy[k]]:=false; 102 end; 103 end; { init } 104 procedure main; 105 begin 106 writeln(ans); 107 end; { main } 108 begin 109 assign(input,'maze.in');reset(input); 110 assign(output,'maze.out');rewrite(output); 111 init; 112 bfs(); 113 main; 114 close(input); 115 close(output); 116 end.
1 //毒药?解药 2 program poison(input,output); 3 var 4 d : array[0..1025] of longint; 5 v : array[0..1025] of boolean; 6 f1,f2 : array[0..200] of integer; 7 q : array[0..2000] of longint; 8 n,m : longint; 9 procedure init; 10 var 11 i,j : longint; 12 tmp : integer; 13 begin 14 fillchar(f1,sizeof(f1),0); 15 fillchar(f2,sizeof(f2),0); 16 readln(n); 17 readln(m); 18 for i:=1 to m do 19 begin 20 for j:=1 to n do 21 begin 22 read(tmp); 23 case tmp of 24 0 : continue; 25 1 : inc(f1[i],1<<(j-1)); 26 -1 : inc(f2[i],1<<(j-1)); 27 end; { case } 28 end; 29 readln; 30 end; 31 end; 32 procedure main; 33 var 34 head,tail : longint; 35 now,newstate,i : longint; 36 begin 37 fillchar(d,sizeof(d),63); 38 fillchar(v,sizeof(v),false); 39 head:=0; 40 tail:=1; 41 q[1]:=(1<<n)-1; 42 v[(1<<n)-1]:=true; 43 d[(1<<n)-1]:=0; 44 while head<tail do 45 begin 46 inc(head); 47 now:=q[head]; 48 v[now]:=false; 49 for i:=1 to m do 50 begin 51 newstate:=(now and (not f1[i])); 52 newstate:=newstate or f2[i]; 53 if d[now]+1<d[newstate] then 54 begin 55 d[newstate]:=d[now]+1; 56 if not v[newstate] then 57 begin 58 inc(tail); 59 q[tail]:=newstate; 60 v[newstate]:=true; 61 end; 62 end; 63 end; 64 end; 65 end; { main } 66 procedure print; 67 begin 68 if d[0]>19950714 then 69 writeln('No Answer') 70 else 71 writeln(d[0]); 72 end; { print } 73 begin 74 assign(input,'poison.in');reset(input); 75 assign(output,'poison.out');rewrite(output); 76 init; 77 main; 78 print; 79 close(input); 80 close(output); 81 end.