插头DP

先%%学长优秀博客:%%

并借用他的定义:

插头DP主要用来处理一系列基于连通性状态压缩的动态规划问题,处理的具体问题有很多种,并且一般数据规模较小。

由于棋盘有很特殊的结构,使得它可以与“连通性”有很强的联系,因此插头DP最常见的应用要数在棋盘模型上的应用了。

直接上题

一、HDU1693 Eat the Trees

题目大意:给出一张n*m有障碍的棋盘,要求用任意条回路遍历整个棋盘,不能经过障碍格子,要求统计不同的行走方案数。

Sample Input
2
6 3
1 1 1
1 0 1
1 1 1
1 1 1
1 0 1
1 1 1
2 4
1 1 1 1
1 1 1 1
Sample Output
Case 1: There are 3 ways to eat the trees.
Case 2: There are 2 ways to eat the trees.

具体过程请参考学长博客,我就不搬了(没素质),上解释代码

重点看Execution函数的分情况讨论的实现 (Execution中判断x==n,y==m的状况可以不写正常转移)

 1 ^:不同则1,相同则0
 2 &:全1则1,有0则0
 3 #include <cstdio>
 4 #include<iostream>
 5 #include <cstring>
 6 using namespace std;
 7 typedef long long LL;
 8 int n,m,bin[20],mp[13][13];
 9 LL f[13][13][(1<<12)+10];//f[i][j][k]表示转移完第i行第j列插头状态为k时的方案数
10 inline void Execution(int x,int y)
11 {
12     int plug1=bin[y-1],plug2=bin[y];//分类bin[y-1]为左插头,bin[y]为上插头
13     if(x==n&&y==m)//最后状态只走0即可
14     {
15         if(mp[x][y])//!障碍且j状态没有插头,^后为有两个插头,及由y-1有两个插头转移
16             f[x][y][0]+=f[x][y-1][plug1^plug2];
17         else//障碍并且转移后保证没有插头及一定合法
18             f[x][y][0]=f[x][y-1][0];//所以没有插头连向它才合法转移
19         return;
20     }
21     for(int j=0;j<bin[m+1];j++)//枚举状态(此时第m+1个插头可以为1)
22         if(mp[x][y])//!障碍
23         {
24             f[x][y][j]+=f[x][y-1][j^plug1^plug2];
25             //plug1^plug2的第y-1,y这两位为1(从0开始)
26             //如果j状态有两个插头,^后为没有插头,及由y-1的没有插头转移
27             //如果j状态没有插头,^后为有两个插头,及由y-1有两个插头转移
28             //如果j状态有一个,^10变01,及由y-1有一个插头且不转向转移来
29             //10变10没法实现,只能排除00/11的情况,直接变
30             if(((j>>(y-1))&1)==((j>>(y))&1)) continue;//都为没插头或都有
31             f[x][y][j]+=f[x][y-1][j];//10变10,及由y-1有一个插头转向转移
32         }
33         else//障碍
34         {
35             if(!(j&plug1)&&!(j&plug2))//y-1处理完后该格没有左插头和上插头,同((!((j>>(y-1))&1))&&(!((j>>y)&1)))
36                 f[x][y][j]=f[x][y-1][j];//及没有插头连向它才合法转移
37             else f[x][y][j]=0;//否则方案数为0
38         }
39 }
40 int main()
41 {
42     int t;
43     scanf("%d",&t);
44     bin[0]=1;
45     for(int i=1;i<=15;i++) //2^最大方案数
46         bin[i]=bin[i-1]<<1;
47     //cout<<bin[8]<<endl;
48     for(int u=1;u<=t;u++)
49     {
50         scanf("%d%d",&n,&m);
51         for(int i=1;i<=n;i++)
52             for(int j=1;j<=m;j++)
53                 scanf("%d",&mp[i][j]);
54         memset(f,0,sizeof(f));
55         f[1][0][0]=1;//*************初始化第0格*****************//
56         for(int i=1;i<=n;i++)
57         {
58             for(int j=1;j<=m;j++) Execution(i,j);//逐格递推
59             if(i!=n)//行间转移:当前行的0到m-1转移到下一行的1到m
60                 for(int j=0;j<bin[m];j++)//bin[m]及第m个插头不可能为1
61                     f[i+1][0][j<<1]=f[i][m][j];//初始化第0格
62         }
63         printf("Case %d: There are %lld ways to eat the trees.\n",u,f[n][m][0]);//最后状态不能再有插头
64     }
65 }

 注意点:(自己的zz错误)

(1)行间转移没判 i!=n        59行

(2)插头从0到m,第m个插头不能为1,写成bin[m+1]         60行

(3)行间转移j<<1写成j>>1,左移1后i+1行的第0位为0         61行

(4)判断都有或没有插头跳过时没&1            30行

原文地址:https://www.cnblogs.com/qwertyuiopasdfghjklzxcvbnm/p/11260734.html