插头DP代码

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<iostream>
  4 using namespace std;
  5 const int cube=(int)1e9;
  6 const int mod=2601;
  7 int n,m;
  8 struct Data_Analysis//有关高精
  9 {
 10     int bit[6];//存储答案的高精数组,bit[i]中不止一个数,会存在一个很大的数
 11     inline void Clear()  {memset(bit,0,sizeof(bit));}
 12     Data_Analysis()  {Clear();}//初始化清空
 13     inline void Set(int t)
 14     {
 15         Clear();
 16         while(t)  {bit[++bit[0]]=t%cube;  t/=cube;}//类似于高精中的对10取模及做乘法,只不过变为对1e9取模做乘法
 17     }
 18     inline int &operator [](int x)  {return bit[x];}
 19     inline void Print()
 20     {
 21         printf("%d",bit[bit[0]]);//bit[0]可能小于0,但是此时也需要输出0,所以需要这么一步
 22         for(int i=bit[0]-1;i>0;i--)  printf("%d",bit[i]);//正常倒着输出
 23         printf("\n");
 24     }
 25     inline Data_Analysis operator + (Data_Analysis b)//高精加
 26     {
 27         Data_Analysis c;  c.Clear();  c[0]=max(bit[0],b[0])+1;//两数相加的位数最多为位数大的那个+1
 28         for(int i=1;i<=c[0];i++)  {c[i]+=bit[i]+b[i];  c[i+1]+=c[i]/cube;  c[i]%=cube;}
 29         while(!c[c[0]])  c[0]--;//排除最高位的0
 30         return c;
 31     }
 32     inline void operator += (Data_Analysis b)  {*this=*this+b;}//*this"返回当前对象的引用 或者说返回该对象本身 还是当前对象的克隆"
 33     inline void operator = (int x)  {Set(x);}//最初bit[0]=0,计数用,所以在最初赋值时也直接用set,把bit[0]留出来
 34 }Ans;
 35 struct Hash_Sheet//有关hash
 36 {
 37     Data_Analysis val[mod];//方案数
 38     int key[mod],size,hash[mod];//所表示的真实状态,元素个数,是hash表中第几个元素
 39     inline void Initialize()//初始化清空
 40     {
 41         size=0;  memset(val,0,sizeof(val));
 42         memset(key,-1,sizeof(key));  memset(hash,0,sizeof(hash));
 43     }
 44     inline void Newhash(int id,int v)  {hash[id]=++size;  key[size]=v;}//在hash表中添加新元素
 45     Data_Analysis &operator [](const int State)
 46     {
 47         for(int i=State%mod;;i=(i+1==mod)?0:i+1)//不停的通过+1的操作向后找,循环查找,直到找到,通过retrun结束for循环
 48         {
 49             if(!hash[i])  Newhash(i,State);//没有就添加新元素
 50             if(key[hash[i]]==State)  return val[hash[i]];//找到该状态,返回该状态下的方案数
 51         }
 52     }
 53 }f[2];
 54 //所有状态均为4进制(以2进制为基础,每两位看作一个整体代表4进制,利用位运算简化操作过程)
 55 //0无插头 1左插头 2右插头
 56 inline int Find(int State,int id)//查找该处插头种类,id代表所在格子是第几列,所以id=x时对应二进制表示中(两位一整体)第x-1个整体
 57 {
 58     return (State>>((id-1)<<1))&3;//(id-1)<<1即(id-1)*2,目的是把需要被查找的位置移到第0,1位处,&3会把除最后两位之外的其他位均置0,并且不改变最后两位
 59 }
 60 inline void Set(int &State,int bit,int val)//修改插头类型
 61 {
 62     bit=(bit-1)<<1;//由于两位一整体,直接算出当前整体的前一位在哪
 63     State|=3<<bit;//先置1
 64     State^=3<<bit;//后置0
 65     State|=val<<bit;//把当前位置改变为val
 66 }
 67 inline int Link(int State,int pos)//查找对应的另一个插头在哪
 68 {
 69     int cnt=0;//标记已经历过的左右插头能否相互抵消,当相互抵消时证明找到了当前插头的对应插头
 70     int Delta=(Find(State,pos)==1)?1:-1;//当前是左插头,就向右找,否则向左找
 71     for(int i=pos;i&&i<=m+1;i+=Delta)//有可能向左,有可能向右,所以要保证1<=i<=m+1(插头编号由1到m+1)
 72     {
 73         int plug=Find(State,i);
 74         if(plug==1)  cnt++;
 75         else if(plug==2)  cnt--;
 76         if(cnt==0)  return i;//一一对应证明找到了相对的左/右插头
 77     }
 78     return -1;//扫了一遍没找到
 79 }
 80 inline void Execution(int x,int y)
 81 {
 82     //((x-1)*m+y)求出是第几个格
 83     int now=((x-1)*m+y)&1;//now只有可能=0/1,只保留最后一位
 84     int last=now^1;//0^1=1 1^1=0记录连着的上一个格子,类似于滚动数组只用0/1
 85     int tot=f[last].size;//上一个格子共有tot种不同状态
 86     f[now].Initialize();//给当前格子清零
 87     for(int i=1;i<=tot;i++)
 88     {
 89         int State=f[last].key[i];//枚举决策上一个格子时的所有不同状态
 90         Data_Analysis Val=f[last].val[i];//状态所对应的方案数
 91         int plug1=Find(State,y),plug2=Find(State,y+1);//寻找当前决策的格子上的两个插头的种类
 92         if(Link(State,y)==-1||Link(State,y+1)==-1)  continue;//没有对应的插头
 93         if(!plug1&&!plug2)//没有插头
 94         {
 95             if(x!=n&&y!=m)  {Set(State,y,1);  Set(State,y+1,2);  f[now][State]+=Val;}//只要不是最后一个格子,就建下插头及右插头,作为1插头和2插头
 96         }
 97         else if(plug1&&!plug2)//只有一个来自左边的插头
 98         {
 99             if(x!=n)  f[now][State]+=Val;//转弯,连接一个下插头
100             if(y!=m)  {Set(State,y,0);  Set(State,y+1,plug1);  f[now][State]+=Val;}//直走,向右
101         }
102         else if(!plug1&&plug2)//只有一个来自上面的插头
103         {
104             if(y!=m)  f[now][State]+=Val;//转弯,连接一个右插头
105             if(x!=n)  {Set(State,y,plug2);  Set(State,y+1,0);  f[now][State]+=Val;}//直走,向下
106         }
107         else if(plug1==1&&plug2==1)//两个左插头,把靠里的那个左插头的右插头变为左插头,两个插头联通,置为没有
108             {Set(State,Link(State,y+1),1);  Set(State,y,0);  Set(State,y+1,0);  f[now][State]+=Val;}
109         else if(plug1==1&&plug2==2)//左边是左插头,右边是右插头
110             {if(x==n&&y==m)  Ans+=Val;}//如果是最后一个格子,直接封口
111         else if(plug1==2&&plug2==1)//左边是右插头,右边是左插头,没影响,直接判联通,置为0,对对应插头无影响
112             {Set(State,y,0);  Set(State,y+1,0);  f[now][State]+=Val;}
113         else if(plug1==2&&plug2==2)//两个右插头,联通置为0,靠里的左插头变右
114             {Set(State,Link(State,y),2);  Set(State,y,0);  Set(State,y+1,0);  f[now][State]+=Val;}
115     }
116 }
117 int main()
118 {
119     scanf("%d%d",&n,&m);
120     if(m>n)  swap(n,m);//用较小数状压
121     f[0].Initialize();  f[0][0]=1;
122     for(int i=1;i<=n;i++)
123     {
124         for(int j=1;j<=m;j++)  Execution(i,j);
125         if(i!=n)//行间转移
126         {
127             int now=(i*m)&1,tot=f[now].size;
128             for(int j=1;j<=tot;j++)  f[now].key[j]<<=2;//两个一整体
129         }
130     }
131     Ans+=Ans;  Ans.Print();
132 }
View Code

 

posted @ 2019-07-31 15:36  hzoi_X&R  阅读(300)  评论(0编辑  收藏  举报