2021長安部分題解
阿新 • • 發佈:2021-10-24
pd頭插
用途
插頭dp主要是用來解決基於連通性狀態壓縮的動態規劃問題,一般來說,就是解決一個網格圖中的迴路方案數的問題,並且資料範圍較小,比如這道模板
方法
定義插頭:路徑是否經過格點的邊,如圖就是一個左插頭
不難發現,對於一個迴路上的所有點,都有且只有兩個插頭,如圖
於是我會爆搜\(\Theta(6^{nm})\)
這顯然不現實,考慮dp轉移
轉移的物件
考慮轉移輪廓線,也就是說一條將我們已經考慮的半圖和未考慮的半圖分開的線,如圖中綠線,藍線表示已經考慮
通常來說,我們採取逐格遞推(少數情況下,我們會考慮逐行遞推)
狀態的表示
最小表示法
障礙為0,第一個連通塊內編號為1,第二個連通塊內編號為2……
(還有一種是將每個連通塊標記成最左邊的列編號)
括號表示法
輪廓線上從左到右4個插頭 a, b, c, d,如果 a, c連通,並且與 b 不連通,那麼 b, d 一定不連通。(對所有棋盤問題都適用,詳見CDQ論文)
那麼不難聯想到括號匹配,用0表示無括號,1,表示左括號,2表示右括號(實現中一般採用4進位制,位運算真香)
一些trival的細節
表示編碼一般考慮用map或hash滾動dp,要不然記憶體會爆
大致方式
就是分類瘋累討論,一個格子只有上下,左右,上左,上右,下左,下右6種情況,於是討論就完了,貼上模板的程式碼
#include<bits/stdc++.h> using namespace std; namespace DEBUG { void debug_out() { cerr << '\n'; } template <typename Head, typename... Tail> void debug_out(Head H, Tail... T) { cerr << ' ' << H, debug_out(T...); } #define debug(...) cerr << '[' << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__) } using namespace DEBUG; typedef long long ll; typedef unsigned long long ull; //#define getchar() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?EOF:*S++) namespace get_out { char B[1<<15],*S=B,*T=B; char op; inline void read_(int &x) { x=0; int fi(1); op=getchar(); while((op<'0'||op>'9')&&op!='-') op=getchar(); if(op=='-') op=getchar(),fi=-1; while(op>='0'&&op<='9') x=(x<<1)+(x<<3)+(op^48),op=getchar(); x*=fi; return; } inline void read_(long long &x) { x=0; int fi(1); op=getchar(); while((op<'0'||op>'9')&&op!='-') op=getchar(); if(op=='-') op=getchar(),fi=-1; while(op>='0'&&op<='9') x=(x<<1)+(x<<3)+(op^48),op=getchar(); x*=fi; return; } inline void read_(double &x) { x=0.0; float fi(1.0),sm(0.0); op=getchar(); while((op<'0'||op>'9')&&op!='-') op=getchar(); if(op=='-') op=getchar(),fi=-1.0; while(op>='0'&&op<='9') x=(x*10.0)+(op^48),op=getchar(); if(op=='.') op=getchar(); int rtim=0; while(op>='0'&&op<='9') sm=(sm*10.0)+(op^48),++rtim,op=getchar(); while(rtim--) sm/=10.0; x+=sm,x*=fi; return; } inline void postive_write(int x) { if(x>9) postive_write(x/10); putchar(x%10|'0'); } inline void postive_write(long long x) { if(x>9) postive_write(x/10); putchar(x%10|'0'); } inline void write_(int x) { if(x<0) x=-x,putchar('-'); postive_write(x); } inline void write_(int x,char ch) { write_(x),putchar(ch); } inline void write_(long long x) { if(x<0) x=-x,putchar('-'); postive_write(x); } inline void write_(long long x,char ch) { write_(x),putchar(ch); } } using get_out::read_; using get_out::write_; #define maxn 15 #define inf 0x7f7f7f7f #define mod 299993 #define hashMaxNum 300000 int n,m,head[hashMaxNum]; int cnt[2],mp[maxn][maxn],edi,edj,now,la; ll ans=0; class hahaha { public: int s[20]; hahaha()=default; hahaha(int state) { s[0]=state&3; for(int i=1;i<=m;++i) s[i]=(state>>(i<<1))&3; } inline int rar() { int state=0; for(int i=1;i<=m;++i) state|=s[i]<<(i<<1); return (state|s[0]); } }; inline hahaha unpack(int state) {return hahaha(state);} inline int rar(hahaha &H){return H.rar();} class hash_T { public: int state[2],nex; ll num[2]; hash_T()=default; }s[hashMaxNum]; inline void insert(int state,ll num) { int tmp=state%mod; for(int i=head[tmp];i;i=s[i].nex) if(state==s[i].state[now]) { s[i].num[now]+=num; return; } s[++cnt[now]].state[now]=state, s[cnt[now]].nex=head[tmp], s[head[tmp]=cnt[now]].num[now]=num; } hahaha now_state,_k; ll lnum; inline void zip_insert() { insert(rar(_k),lnum),_k=now_state; } inline void solve() { register int i,j,k; int west,north; insert(0,1); for(i=1;i<=n;++i) for(j=1;j<=m;++j) { la=now,now^=1;//renew state cnt[now]=0,memset(head,0,sizeof(head));//init for(k=1;k<=cnt[la];++k) { now_state=unpack(s[k].state[la]),_k=now_state; lnum=s[k].num[la],west=now_state.s[0],north=now_state.s[j]; if(!mp[i][j]) {if(!west&&!north) insert(rar(_k),lnum);continue;} if(!west&&!north) { if(mp[i][j+1]&&mp[i+1][j]) _k.s[0]=2, _k.s[j]=1,//掛過 zip_insert(); continue; } if(!west&&north) { if(mp[i+1][j]) insert(rar(_k),lnum); if(mp[i][j+1]) _k.s[0]=north, _k.s[j]=0, zip_insert(); continue; } if(west&&!north) { if(mp[i][j+1]) insert(rar(_k),lnum); if(mp[i+1][j]) _k.s[0]=0, _k.s[j]=west, zip_insert(); continue; } if(west==1&&north==2) { _k.s[0]=_k.s[j]=0; bool flag=0; for(int pos=0;pos<=m;++pos) if(_k.s[pos]) { flag=1; break; } if(!flag&&i==edi&&j==edj) ans+=lnum; continue; } if(west==2&&north==1) { _k.s[0]=_k.s[j]=0; zip_insert(); continue; } if(west==1&&north==1) { int nm=1,pos=j+1; for(;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; zip_insert(); continue; } if(west==2&&north==2) { int nm=-1,pos=j-1; for(;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; zip_insert(); continue; } } } } signed main() { register char ch[20]; read_(n),read_(m); for(int i=1;i<=n;++i) { scanf("%s",ch+1); for(int j=1;j<=m;++j) ((ch[j]=='.'&&(mp[i][j]=1,edi=i,edj=j))||(mp[i][j]=0)); } solve(); cout<<ans<<'\n'; return 0; }