浅谈状态压缩的应用

来源:互联网 发布:json null 转为空 编辑:程序博客网 时间:2024/05/29 15:09

前言

有些问题,时刻需要知道问题的当前状态,所以需要的每一个状态进行保存,而在计算机中,简洁快速的二进制受到大家表示状态时的青睐,最近在学习中总结了一下状态压缩,它不仅应用于我们常提的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])) then100      begin101         newstate:=q[head].state or key[newx,newy];102         if not v[newx,newy,newstate] then103         begin104            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) then111            begin112           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)  then118      begin119         newstate:=q[head].state or key[newx,newy];120         if not v[newx,newy,newstate] then121         begin122            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) then129            begin130           ans:=q[tail].dist;131           exit;132            end;133         end;134      end;135       end;136    end;137 end; { bfs }138 procedure print;139 begin140    if ans=0 then141       writeln(-1)142    else143       writeln(ans);144 end; { print }145 begin146    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 do101            can[i+fx[k],j+fy[k]]:=false;102      end;103 end; { init }104 procedure main;105 begin106    writeln(ans);107 end; { main }108 begin109    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 var11    i,j : longint;12    tmp : integer;13 begin14    fillchar(f1,sizeof(f1),0);15    fillchar(f2,sizeof(f2),0);16    readln(n);17    readln(m);18    for i:=1 to m do19    begin20       for j:=1 to n do21       begin22      read(tmp);23      case tmp of24        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 var34    head,tail      : longint;35    now,newstate,i : longint;36 begin37    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 do45    begin46       inc(head);47       now:=q[head];48       v[now]:=false;49       for i:=1 to m do50       begin51      newstate:=(now and (not f1[i]));52      newstate:=newstate or f2[i];53      if d[now]+1<d[newstate] then54      begin55         d[newstate]:=d[now]+1;56         if not v[newstate] then57         begin58            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 begin68    if d[0]>19950714 then69       writeln('No Answer')70    else71       writeln(d[0]);72 end; { print }73 begin74    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.
复制代码

原创粉丝点击