辣鸡蒟蒻\(hzf\)终于学会插头dp辣!!!!!!
不过这玩意是真的毒瘤
Ⅰ、前置知识
dp???
状压dp???算了反正写了前置知识也懒得讲
Ⅱ、抛出问题
洛谷板子
题目背景
ural 1519
陈丹琦《基于连通性状态压缩的动态规划问题》中的例题
题目描述
给出\(n\times m\)的方格,有些格子不能铺线,其它格子必须铺,形成一个闭合回路。问有多少种铺法?
输入输出格式
输入格式:
第1行,\(n,m(2\leq n,m\leq12)\)
从第2行到第\(n+1\)行,每行一段字符串(\(m\)个字符),"*"表不能铺线,"."表必须铺
输出格式:
输出一个整数,表示总方案数
输入输出样例
输入样例#1:
4 4
**..
....
....
....
输出样例#1:
2
输入样例#2:
4 4
....
....
....
....
输出样例#2:
6
Ⅲ、分析问题
题目剧透才是最sao的
论文自行百度吧QAQ
顺便吸一口CDQ巨气
以上废话
插头dp,顾名思义,就是一堆插头搞来搞去然后dp一下
1、什么是插头
插头dp一般针对于网格图,在此道题中是在网格图中求哈密顿回路(不知道的自行百度吧QAQ)
如图,第一个对应的是一个上插头和一个左插头,第四个图对应的是一个下插头和一个右插头,左边上面是空插头
我们在这道题中要做的,就是给每一个格子确定插头,使得它们形成一条哈密顿回路,并且输出答案
爆搜?\(O(6^{n\times m})\)就算你有一些神奇的剪枝优化可卡常技巧,也能分分钟炸飞?
如何解决?就要用接下来这个神奇的东西
2、轮廓线
简单地说,轮廓线就是已决策格子和未决策格子的分界线
看图~
如图,\(4\times4\)的方格,左上角两个格子为障碍格子(也就是样例1)
红色的是已决策出来的可能会与下面未决策格子形成哈密顿回路的线条(可能不止一条,记一个\(cnt\)就行),蓝色则是轮廓线,可以观察到,当前决策的格子是位于第三行最后一个的格子,并且每一个轮廓线上都对应有5个插头,4个下插头和一个右插头
如图,四个下插头有,右插头为空插头
接下来考虑如何转移
因为是插头dp,所以肯定要记录插头之间的联通性(我也不知道怎么想出来的QAQ,人类智慧吧)
考虑使用括号序列
左括号表示一条联通线的点,用1表示;右括号表示一条联通线的终点,用2表示;无插头用0表示
如图
于是插头dp转移的实质就是
由上一条轮廓线转移到下一条轮廓线,其中只涉及当前格子左侧和上方插头的变化
(由蓝色轮廓线转移到绿色轮廓线)
转移过程中,其实轮廓线左侧三个插头都并没有变化
所以,轮廓线的转移,终点就是当前格子的上插头和左插头转移到下插头和右插头!
思路都懂了,接下来就到友好毒瘤的分类讨论环节
出于方便,比如轮廓线的状态如\(10\ 10\ 01\ 01\ 00\)最后两位为轮廓线上右插头的状态,剩下的表示下插头
对着图看吧
inline hahaha unpack(int sta){
hahaha rt;
rt._s[0]=sta&3;
F(i,1,m)
rt._s[i]=(sta>>(i<<1))&3;
return rt;
}
inline int WinRAR(hahaha rt){
int sta=0;
F(i,1,m)
sta=sta|(rt._s[i]<<(i<<1));
sta|=rt._s[0];
return sta;
}//压缩解压代码写在前面
//对着上文看吧
考虑到每个插头有三种状态\(0,1,2\)而四进制的位运算快到飞起,所以用四进制存状态
设dp[i][j][sta]表示在(i,j)这个格子,轮廓线状态为sta的方案数
很显然\(12\times12\times4^{12+1}\)炸飞不成问题
所以用滚动数组
因为当前转移只跟\(dp[i][j-1][\_sta]\)有关所以将前两位搞成滚动数组
上一个\(dp\)是\(lst\),当前\(dp\)为\(now\)
这样就优化成了\(2\times4^{12+1}\)然而还是轻松MLE
把最后一维哈希一下即可
本文采用挂表法哈
1、当前格子为障碍格子
这个很好转移,直接判断\(mp[i+1][j]\)和\(mp[i][j+1]\)是否可以放插头
if(!mp[i][j]){//太显然了
if(!west&&!north)//west左侧插头,north上方插头
ins(WinRAR(_k),lnum);//加入哈希表
goto ctn;//continue
}
2、左边和上面都没有插头
看一下右边和下面是不是障碍,然后直接连上,相当于创造一个新的线条
if(!west&&!north){
if(mp[i+1][j]&&mp[i][j+1]){
_k._s[0]=2;
_k._s[j]=1;//手推一下即可
ins(WinRAR(_k),lnum);
_k=now_pnt;//还原_k
}
goto ctn;//continue
}
3、左边或上面的插头中只有一个是空插头
这里就有两种情况了,可以直接延长,也可以拐个弯,相当于延续原来的
if(!west&&north){
if(mp[i+1][j])
ins(WinRAR(_k),lnum);
if(mp[i][j+1]){
_k._s[0]=north;//手推一下
_k._s[j]=0;
ins(WinRAR(_k),lnum);
_k=now_pnt;
}
goto ctn;//continue
}
if(west&&!north){
if(mp[i][j+1])
ins(WinRAR(_k),lnum);
if(mp[i+1][j]){
_k._s[0]=0;
_k._s[j]=west;
ins(WinRAR(_k),lnum);
_k=now_pnt;
}
goto ctn;//continue
}
4、左边为终点,上面为起点
相当于把两条线连在一起,都附成0即可
if(west==2&&north==1){
_k._s[0]=_k._s[j]=0;
ins(WinRAR(_k),lnum);
_k=now_pnt;
goto ctn;
}
5、都为起点/终点
连起来,再找到其中一条的另一端,附成相对的值
if(west==1&&north==1){
int nm=1,pos;
for(pos=j+1;pos<=m;pos++){
nm+=_k._s[pos]==1?1:_k._s[pos]==2?-1:0;
if(!nm)
break;
}
_k._s[pos]=1;
_k._s[0]=_k._s[j]=0;
ins(WinRAR(_k),lnum);
_k=now_pnt;
goto ctn;
}
if(west==2&&north==2){
int nm=-1,pos;
for(pos=j-1;pos;pos--){
nm+=_k._s[pos]==1?1:_k._s[pos]==2?-1:0;
if(!nm)
break;
}
_k._s[pos]=2;
_k._s[0]=_k._s[j]=0;
ins(WinRAR(_k),lnum);
_k=now_pnt;
goto ctn;
}
6、左边为起点,上面为终点
手推后发现只有一种可能,即左边与上面联通
既然如果放了之后就构成一个联通快,就不能随便放,会出事的
放之前检查一下是不是最后一个非障碍格子,并且是不是最后插头都为空
if(west==1&&north==2){
_k._s[0]=_k._s[j]=0;
bool flag=0;
F(pos,0,m)
if(_k._s[pos]){
flag=1;
break;
}
if(!flag&&i==edi&&j==edj)
ans+=lnum;
}
完整代码:
#include<bits/stdc++.h>
#define int long long
#define INF 2147483647
#define mem(i,j) memset(i,j,sizeof(i))
#define F(i,j,n) for(register int i=j;i<=n;i++)
#define Hashmod 299993
using namespace std;
struct hahaha{
int _s[20];
};
struct Hash{
int sta[2],num[2],nxt;
}s[300010];
int n,m,head[300010],cnt[2],mp[20][20],edi,edj,now,lst,ans=0;
char ch[20];
inline int read(){
int datta=0;char chchc=getchar();bool okoko=0;
while(chchc<'0'||chchc>'9'){if(chchc=='-')okoko=1;chchc=getchar();}
while(chchc>='0'&&chchc<='9'){datta=datta*10+chchc-'0';chchc=getchar();}
return okoko?-datta:datta;
}
inline void ins(int sta,int num){
int tmp=sta%Hashmod;
for(int i=head[tmp];i;i=s[i].nxt)
if(sta==s[i].sta[now]){
s[i].num[now]+=num;
return ;
}
s[++cnt[now]].sta[now]=sta;
s[cnt[now]].num[now]=num;
s[cnt[now]].nxt=head[tmp];
head[tmp]=cnt[now];
}
inline hahaha unpack(int sta){
hahaha rt;
rt._s[0]=sta&3;
F(i,1,m)
rt._s[i]=(sta>>(i<<1))&3;
return rt;
}
inline int WinRAR(hahaha rt){
int sta=0;
F(i,1,m)
sta=sta|(rt._s[i]<<(i<<1));
sta|=rt._s[0];
return sta;
}
inline void solve(){
ins(0,1);
F(i,1,n){
F(j,1,m){
lst=now;
now^=1;
cnt[now]=0;
mem(head,0);
F(k,1,cnt[lst]){
hahaha now_pnt=unpack(s[k].sta[lst]),_k=now_pnt;
int lnum=s[k].num[lst],west=now_pnt._s[0],north=now_pnt._s[j];
if(!mp[i][j]){
if(!west&&!north)
ins(WinRAR(_k),lnum);
goto ctn;
}
if(!west&&!north){
if(mp[i+1][j]&&mp[i][j+1]){
_k._s[0]=2;
_k._s[j]=1;
ins(WinRAR(_k),lnum);
_k=now_pnt;
}
goto ctn;
}
if(!west&&north){
if(mp[i+1][j])
ins(WinRAR(_k),lnum);
if(mp[i][j+1]){
_k._s[0]=north;
_k._s[j]=0;
ins(WinRAR(_k),lnum);
_k=now_pnt;
}
goto ctn;
}
if(west&&!north){
if(mp[i][j+1])
ins(WinRAR(_k),lnum);
if(mp[i+1][j]){
_k._s[0]=0;
_k._s[j]=west;
ins(WinRAR(_k),lnum);
_k=now_pnt;
}
goto ctn;
}
if(west==2&&north==1){
_k._s[0]=_k._s[j]=0;
ins(WinRAR(_k),lnum);
_k=now_pnt;
goto ctn;
}
if(west==1&&north==1){
int nm=1,pos;
for(pos=j+1;pos<=m;pos++){
nm+=_k._s[pos]==1?1:_k._s[pos]==2?-1:0;
if(!nm)
break;
}
_k._s[pos]=1;
_k._s[0]=_k._s[j]=0;
ins(WinRAR(_k),lnum);
_k=now_pnt;
goto ctn;
}
if(west==2&&north==2){
int nm=-1,pos;
for(pos=j-1;pos;pos--){
nm+=_k._s[pos]==1?1:_k._s[pos]==2?-1:0;
if(!nm)
break;
}
_k._s[pos]=2;
_k._s[0]=_k._s[j]=0;
ins(WinRAR(_k),lnum);
_k=now_pnt;
goto ctn;
}
if(west==1&&north==2){
_k._s[0]=_k._s[j]=0;
bool flag=0;
F(pos,0,m)
if(_k._s[pos]){
flag=1;
break;
}
if(!flag&&i==edi&&j==edj)
ans+=lnum;
}
ctn:;
}
}
}
}
signed main(){
n=read();m=read();
F(i,1,n){
scanf("%s",ch+1);
F(j,1,m)
((ch[j]=='.'&&(mp[i][j]=1,edi=i,edj=j))||(mp[i][j]=0));
}
solve();
printf("%lld\n",ans);
return 0;
}