终于来补插头DP的坑了,咕了好久,主要是因为博猪代码实现能力太弱,而网上的大神们都只讲分类讨论。。。
只放代码了:
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define A 1100000 5 #define mod 299989 6 #define P 8 7 #define N 100000000 8 ll n,m; 9 inline ll find(ll state,ll id){ 10 return (state>>((id-1)<<1))&3; 11 }//看当前插头究竟是什么插头 12 //因为是四进制每两位代表一个状态 13 struct bignum 14 { 15 ll n[10],l; 16 bignum(){l=1,memset(n,0,sizeof(n));} 17 void clear(){while(l>1&&!n[l-1]) l--;} 18 void print(){ 19 printf("%lld",n[l-1]); 20 for(ll i=l-2;i>=0;i--) 21 printf("%0*lld",P,n[i]); 22 printf("\n"); 23 } 24 bignum operator +(bignum x)const{ 25 bignum t=*this; 26 if(t.l<x.l) t.l=x.l; 27 t.l++; 28 for(ll i=0;i<t.l;i++){ 29 t.n[i]+=x.n[i]; 30 if(t.n[i]>=N){ 31 t.n[i+1]+=t.n[i]/N; 32 t.n[i]%=N; 33 } 34 } 35 t.clear(); 36 return t; 37 } 38 bignum operator =(ll x){ 39 l=0; 40 while(x){ 41 n[l++]=x%N; 42 x/=N; 43 } 44 return *this; 45 } 46 inline void operator +=(bignum b){*this=*this+b;} 47 }Ans; 48 struct hash_map 49 { 50 bignum val[mod]; 51 ll key[A],hash[mod],size; 52 inline void init(){ 53 memset(val,0,sizeof(val)); 54 memset(key,-1,sizeof(key));size=0; 55 memset(hash,0,sizeof(hash)); 56 } 57 inline void newhash(ll id,ll v){hash[id]=++size;key[size]=v;} 58 bignum &operator [] (const ll &state){ 59 for(ll i=state%mod;;i=(i+1==mod)?0:i+1) 60 { 61 if(!hash[i]) newhash(i,state); 62 if(key[hash[i]]==state) return val[hash[i]]; 63 } 64 } 65 }f[2]; 66 inline void Set(ll &state,ll bit,ll val){ 67 bit=(bit-1)<<1; 68 state|=3<<bit; 69 state^=3<<bit; 70 //把state高位先赋成0再把它赋成别的 71 state|=val<<bit; 72 //state表示状态 73 //因为插头的编号为0--m所以bit要-1 74 //因为是四进制,所以3<< 75 //全都是4进制 76 //2<<bit 77 //1<<bit 78 //貌似还能理解 79 //每两位代表一个具体状态 80 } 81 ll link(ll state,ll pos){ 82 //找到对应的插头(用括号匹配的方式)然后 83 ll cnt=0,delta=(find(state,pos)==1)?1:-1; 84 //如果是左括号应该向右寻找右括号 85 //如果是右括号应该向左寻找左括号 86 for(ll i=pos;i&&i<=m+1;i+=delta)//一共m+1个插头 87 { 88 ll plug=find(state,i); 89 if(plug==1) 90 cnt++;//左括号数量++ 91 else if(plug==2) 92 cnt--;//右括号数量++ 93 if(cnt==0)//当左括号数量与右括号数量相等时找到匹配 94 return i;//找到了与当前插头对应的插头 95 } 96 return -1; 97 //当前状态是非法的找不到与之对应的插头 98 } 99 inline void education(ll x,ll y){ 100 ll now=((x-1)*m+y)&1,last=now^1,tot=f[last].size; 101 f[now].init(); 102 for(ll i=1;i<=tot;i++){ 103 // printf("i=%lld\n",i); 104 ll state=f[last].key[i];//key状态 105 bignum Val=f[last].val[i];//取出上一次权值(方案数) 106 ll plug1=find(state,y),plug2=find(state,y+1); 107 //0--m编号,寻找轮廓线上编号y-1,y对应的插头 108 //至于为什么是y y+1,因为在上面函数里进行了减1 109 //编号为y-1是左右插头,y代表上下插头 110 if(link(state,y)==-1||link(state,y+1)==-1) 111 continue; 112 //当前括号无法找到匹配无解 113 if(!plug1&&!plug2){ 114 if(x!=n&&y!=m){ 115 //如果没有插头,直接拽过来两个插头相连(此题保证必须连通) 116 Set(state,y,1); 117 //在轮廓线上位置为y-1添加一个左括号 118 Set(state,y+1,2); 119 //y位置添加一个右括号 120 f[now][state]+=Val; 121 } 122 } 123 else if(plug1&&!plug2){ 124 //拥有左右插头没有上下插头 125 //两种转移方式,转弯向下走 126 //这样插头状态不变 127 if(x!=n) 128 f[now][state]+=Val; 129 //向右连接一个插头 130 //向右推进要发生改变 131 if(y!=m){ 132 Set(state,y,0); 133 Set(state,y+1,plug1); 134 f[now][state]+=Val; 135 } 136 } 137 else if(!plug1&&plug2){ 138 //拥有上下插头而没有左右插头 139 //两种转移方式,向右转移 140 //这样插头状态不变 141 if(y!=m) 142 f[now][state]+=Val; 143 //向右连接一个插头 144 if(x!=n){ 145 Set(state,y,plug2); 146 Set(state,y+1,0); 147 //plug2是左右插头让上下方向转弯 148 f[now][state]+=Val; 149 } 150 } 151 else if(plug1==1&&plug2==1){ 152 //两个左括号插头相连接,然后让最靠左的右括号插头变成左括号插头 153 Set(state,link(state,y+1),1); 154 Set(state,y,0); 155 Set(state,y+1,0); 156 f[now][state]+=Val; 157 } 158 else if(plug1==1&&plug2==2){ 159 //右插头是左括号插头,上插头是右括号插头,连在一起 160 //构成回路 161 if(x==n&&y==m) 162 Ans+=Val; 163 } 164 else if(plug1==2&&plug2==1){ 165 //无脑合并 166 Set(state,y,0); 167 Set(state,y+1,0); 168 f[now][state]+=Val; 169 } 170 else if(plug1==2&&plug2==2){ 171 //当你有左右插头右括号插头,上下插头为右插头 172 //合并为1,将最靠右左插头变为右插头 173 Set(state,link(state,y),2); 174 Set(state,y,0); 175 Set(state,y+1,0); 176 f[now][state]+=Val; 177 } 178 } 179 } 180 int main(){ 181 scanf("%lld%lld",&n,&m); 182 if(n==1||m==1){printf("1\n");return 0;} 183 if(m>n) swap(n,m); 184 f[0].init();f[0][0]=1; 185 for(ll i=1;i<=n;i++) 186 { 187 for(ll j=1;j<=m;j++) 188 education(i,j); 189 if(i!=n){ 190 ll now=(i*m)&1,tot=f[now].size; 191 for(ll j=1;j<=tot;j++) 192 f[now].key[j]<<=2; 193 } 194 } 195 Ans+=Ans; 196 Ans.print(); 197 }
zzn大神解读版
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int LIM=300005,Has=299989; int n,m,e1,e2,v,u;LL ans; int mp[15][15],bin[15],t[2];LL w[2][LIM]; int head[300005],to[2][LIM],Next[LIM];//t:状态总数,w:该状态的方案总数,to:各种状态 void ins(int zt,LL num) {//卓越的哈希技术 int tmp=zt%Has+1; for(int i=head[tmp];i;i=Next[i]) if(to[u][i]==zt) {w[u][i]+=num;return;} Next[++t[u]]=head[tmp],head[tmp]=t[u]; to[u][t[u]]=zt,w[u][t[u]]=num; } void work() { t[u]=1,w[u][1]=1,to[u][1]=0; for(int i=1;i<=n;++i) { for(int j=1;j<=t[u];++j) to[u][j]<<=2;//切换行了 for(int j=1;j<=m;++j) { v=u,u^=1; memset(head,0,sizeof(head)),t[u]=0; for(int k=1;k<=t[v];++k) { int zt=to[v][k],b1=(zt>>(j*2-2))%4,b2=(zt>>(j*2))%4;//提取关键格子上的两段轮廓线状态 LL num=w[v][k]; if(!mp[i][j]) {if(!b1&&!b2) ins(zt,num);} else if(!b1&&!b2) {if(mp[i+1][j]&&mp[i][j+1]) ins(zt+bin[j-1]+2*bin[j],num);} else if(!b1&&b2) { if(mp[i][j+1]) ins(zt,num); if(mp[i+1][j]) ins(zt-bin[j]*b2+bin[j-1]*b2,num); } else if(b1&&!b2) { if(mp[i][j+1]) ins(zt-bin[j-1]*b1+bin[j]*b1,num); if(mp[i+1][j]) ins(zt,num); } else if(b1==1&&b2==1) { int kl=1; for(int ttt=j+1;ttt<=m;++ttt) { if((zt>>(ttt*2))%4==1) ++kl; if((zt>>(ttt*2))%4==2) --kl; if(!kl) {ins(zt-bin[j]-bin[j-1]-bin[ttt],num);break;} } } else if(b1==2&&b2==2) { int kl=1; for(int ttt=j-2;ttt>=0;--ttt) { if((zt>>(ttt*2))%4==1) --kl; if((zt>>(ttt*2))%4==2) ++kl; if(!kl) {ins(zt+bin[ttt]-2*bin[j]-2*bin[j-1],num);break;} } } else if(b1==2&&b2==1) ins(zt-2*bin[j-1]-bin[j],num); else if(i==e1&&j==e2) ans+=num; } } } } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) { char ch=getchar(); while(ch^‘*‘&&ch^‘.‘) ch=getchar(); if(ch==‘.‘) mp[i][j]=1,e1=i,e2=j; } bin[0]=1;for(int i=1;i<=12;++i) bin[i]=bin[i-1]<<2; work(),printf("%lld\n",ans); return 0; }
原文地址:https://www.cnblogs.com/toot-wjh/p/11298150.html
时间: 2024-10-07 05:21:39