辣雞蒟蒻\(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;
}