[整理]插頭 dp
阿新 • • 發佈:2021-06-27
全名是基於連通性狀態壓縮的動態規劃問題,從這個名字可以看出它的特徵:是狀壓 dp(廢話)、維護的是連通性。
怎麼理解呢?接下來以洛谷模板題為例講解它的維護方法。
考慮如何維護一條輪廓線,我們需要引入插頭的概念:一個格子的插頭是指它可以連出去的某條邊(或者理解為讓輪廓線插♂進來的地方)。對於這題我們應從上到下從左到右遞推,就應該考慮上一行對下一行有什麼影響:需要記錄每一行的插頭情況和連通情況(不理解沒關係,下面有詳細圖解)。
具體來說,插頭情況就是每個格子有沒有插頭,連通情況是指每個插頭對應的連通輪廓線情況。例如:下圖中紅線處的插頭情況可以狀壓成 \(1111\),連通情況是 \((1,2)\)
我們很容易發現,插頭之間的連通情況就像括號匹配一樣不會出現交叉,由此產生了括號表示法:用左/右括號代表每個連通分量中從左邊/右邊插出來的插頭,空代表沒有插頭,這樣上圖的連通情況又可以表示為 \(\texttt{(())}\),然後我們發現其實插頭情況已經在連通情況中表示出來了,所以去掉這一維。
然後我們就可以大力分類討論當前格子的左/上的插頭情況了:
- 當前格子為障礙,當且僅當都沒有插頭時才能轉移;
- 當前格子無障礙且都沒有插頭時,當前格子的右/下插頭都應連線;
- 有一個方向有插頭時,可以選擇直走或拐彎;
- 都有插頭且括號型別相同時,遍歷找到對應括號轉移(以都為左括號為例);
- 都有插頭且左插頭為右括號、上插頭為左括號時,閉合插頭;
- 都有插頭且左插頭為左括號、上插頭為右括號時,當且僅當轉移到結尾才能閉合。
由於有三種標記,狀壓時應使用高於 \(2\) 的進位制,為了方便位運算這裡採用四進位制狀壓。
然而我們發現這裡的空間複雜度是 \(\mathcal O(nm4^m)\),於是滾動陣列。但是時空複雜度依然巨大多,我們發現有很多的狀態是沒用的,可以不用暴力列舉上一行轉移而是用 Hash 表之類的東西存下來可行狀態。
模板題核心常數巨大程式碼(第一次見優化比本體難寫):
const int N=100003; int n,m,a[20][20],ex,ey,ans; struct Edge { int to[2],nxt,wei[2]; }e[N]; int hd[N],cnt[2]; il void ade(int cur,int st,int val){ int u=st%N; for(rg int i=hd[u];i;i=e[i].nxt){ if(e[i].to[cur]==st)return (void)(e[i].wei[cur]+=val); } e[++cnt[cur]].to[cur]=st,e[cnt[cur]].wei[cur]=val; e[cnt[cur]].nxt=hd[u],hd[u]=cnt[cur]; return; } #define D(st,k) (st>>(k<<1)&3) #define G(val,k) (val<<(k<<1)) signed main(){ Read(n),Read(m); for(rg int i=1;i<=n;i++){ char ipt[20];scanf("%s",ipt+1); for(rg int j=1;j<=m;j++){ if(ipt[j]=='.')a[i][j]=1,ex=i,ey=j; } } int cur=0,lst=1;ade(cur,0,1); for(rg int i=1;i<=n;i++){ for(rg int j=1;j<=cnt[cur];j++)e[j].to[cur]<<=2; for(rg int j=1;j<=m;j++){ lst=cur,cur^=1,cnt[cur]=0,memset(hd,0,sizeof(hd)); for(rg int c=1;c<=cnt[lst];c++){ int st=e[c].to[lst],v=e[c].wei[lst]; int lt=D(st,j-1),up=D(st,j),now; if(!a[i][j]){//1 if(!lt&&!up)ade(cur,st,v); }else if(!lt&&!up){//2 now=st+G(1,j-1)+G(2,j); if(a[i+1][j]&&a[i][j+1])ade(cur,now,v); }else if(!(lt&&up)){//3 if(lt){ if(a[i][j+1])ade(cur,st-G(lt,j-1)+G(lt,j),v); if(a[i+1][j])ade(cur,st,v); }else { if(a[i+1][j])ade(cur,st+G(up,j-1)-G(up,j),v); if(a[i][j+1])ade(cur,st,v); } }else if(lt==up){//4 now=st-G(lt,j-1)-G(up,j); if(lt==1){ for(rg int k=j+1,tp=1;k<=m;k++){ int u=D(st,k); if(u==1)tp++; else if(u==2)tp--; if(!tp){ ade(cur,now-G(1,k),v);break; } } }else { for(rg int k=j-2,tp=1;k>0;k--){ int u=D(st,k); if(u==2)tp++; else if(u==1)tp--; if(!tp){ ade(cur,now+G(1,k),v);break; } } } }else if(lt==2&&up==1){//5 ade(cur,st-G(lt,j-1)-G(up,j),v); }else if(i==ex&&j==ey)ans+=v;//6 } } } cout<<ans<<endl; KafuuChino HotoKokoa }