现在的位置: 首页 > 综合 > 正文

浅谈状态压缩的应用

2017年12月17日 ⁄ 综合 ⁄ 共 9836字 ⁄ 字号 评论关闭

前言

有些问题,时刻需要知道问题的当前状态,所以需要的每一个状态进行保存,而在计算机中,简洁快速的二进制受到大家表示状态时的青睐,最近在学习中总结了一下状态压缩,它不仅应用于我们常提的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.
复制代码

【上篇】
【下篇】

抱歉!评论已关闭.