适用范围:数据范围极小,与连通性有关,网格图。
转移时用括号序来表示插头情况,然后分类讨论一下当前格子的左轮廓与上轮廓,也就是看有没有右插头与下插头,主要分为三大类:
①两个插头都没有
②只有一个插头
③两个插头都有
再分别讨论一下插头是 “ ( ” “(” “(”还是 “ ) ” “)” “)”即可。
当一层转移完时,要从这一层的最后一个格子跳到下一层的第一个格子。那么当前所有的合法状态的四进制表示的最高位一定为0,因为最右边那条边一定是没有插头的。而下一层的最低位也一定为0,因为下一层第一个格子的左边一定没有插头。
所以把当前这一层的合法的状态左移一位(四进制就是乘4),就可以得到下一层的初始状态了。
两个插头都存在的时候,可能会把一些边联通,所以要找到被连通的边把它对应的插头改掉,并且把连通的地方的插头删掉。
其他情况都比较好讨论,在参考博客里有详细说明 o r z orz orz。
由于一层中有很多无用状态,所以用一个数据结构来存储合法状态,每次就从上一层的合法状态集合中取出一个状态来转移。所以需要支持以下操作:
①遍历所有元素
②支持插入一个关键字。(添加一个合法状态)
③支持修改一个关键字的值。
(因为可能有多个不同状态转移到相同状态。)
采用哈希表。
s
e
t
set
set应该也可以吧。
a
d
d
(
u
,
s
t
a
,
v
a
l
)
add(u,sta,val)
add(u,sta,val)表示在关键字
u
u
u中插入一个状态为
s
t
a
sta
sta,值为
v
a
l
val
val的元素。然后就没了。
d
w
n
dwn
dwn和
r
g
t
rgt
rgt分别提取的是上一个状态的上左两条边。
现在要转移到下右两条边。
#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
using namespace std;
cs int base=4,M=14,mod=599087,N=6e5+10;
int n,m,cur,pre,last_x,last_y,mp[M][M],state[2][N],power[M];ll ans,dp[2][N];
namespace Hash_Table{
int Head[N],Next[N],cnt[2]={0};
inline void add(int u,int sta,ll val){
Next[++cnt[cur]]=Head[u],Head[u]=cnt[cur];
state[cur][cnt[cur]]=sta,dp[cur][cnt[cur]]=val;
}
inline void insert(int sta,ll val){
int u=sta%mod+1;
for(int re i=Head[u];i;i=Next[i])
if(state[cur][i]==sta){dp[cur][i]+=val;return;}
add(u,sta,val);
}
}
using namespace Hash_Table;
inline int get(){
char ch=getchar();
while(ch!='.'&&ch!='*') ch=getchar();
return ch=='.';
}
inline void solve(){
insert(0,1);
for(int re i=1;i<=n;++i){
for(int re j=1;j<=cnt[cur];++j) state[cur][j]<<=2;
for(int re j=1;j<=m;++j){
memset(Head,0,sizeof Head);
pre=cur,cur^=1,cnt[cur]=0;
int re nowsta,dwn,rgt;ll re nowans;
for(int re k=1;k<=cnt[pre];++k){
nowsta=state[pre][k],nowans=dp[pre][k];
dwn=(nowsta>>(j<<1))%base,rgt=(nowsta>>((j-1)<<1))%base;
if(!mp[i][j]){
if(!(dwn+rgt)) insert(nowsta,nowans);
}else if(!(dwn+rgt)){//有障碍
if(mp[i+1][j]&&mp[i][j+1]) insert(nowsta+power[j-1]+power[j]*2,nowans);
}else if((!dwn)&&rgt){
if(mp[i+1][j]) insert(nowsta,nowans);//向下走不改变括号序。
if(mp[i][j+1]) insert(nowsta-power[j-1]*rgt+power[j]*rgt,nowans);//向右走相当于把末端在括号序上往右移动一位。
}else if(dwn&&(!rgt)){
if(mp[i][j+1]) insert(nowsta,nowans);//向右走不改变括号序。
if(mp[i+1][j]) insert(nowsta-power[j]*dwn+power[j-1]*dwn,nowans);//与上面相反。
}else if(dwn==1&&rgt==1){
int re count=1;//找到连出去的第一个2,改为1,并把交接处的插头删掉
for(int re l=j+1;l<=m;++l){
if((nowsta>>(l<<1))%4==1) ++count;
else if((nowsta>>(l<<1))%4==2) --count;
if(!count){insert(nowsta-power[l]-power[j]-power[j-1],nowans);break;}
}
}else if(dwn==2&&rgt==2){
int re count=1;//找到连出去的第一个1,改为2,并把交接处的插头删掉
for(int re l=j-2;l>=0;--l){
if((nowsta>>(l<<1))%4==1) --count;
else if((nowsta>>(l<<1))%4==2) ++count;
if(!count){insert(nowsta+power[l]-power[j]*2-power[j-1]*2,nowans);break;}
}
}else if(dwn==1&&rgt==2){
insert(nowsta-power[j]-power[j-1]*2,nowans);
}else if(dwn==2&&rgt==1) if(i==last_x&&j==last_y) ans+=nowans;
}
}
}
}
int main(){
// freopen("5056.in","r",stdin);
scanf("%d%d",&n,&m),power[0]=1;
for(int i=1;i<M;++i) power[i]=power[i-1]<<2;
for(int re i=1;i<=n;++i)
for(int re j=1;j<=m;++j)
if(mp[i][j]=get()) last_x=i,last_y=j;
solve(),printf("%lld",ans);
}