插头 DP
插头 DP 实际上就是维护平面图的连通性计数问题。
P5074 Eat the Trees
先看一个简化版的。
由于 \(n\) 很小,因此考虑类似状压的方式。对于每一行,我们维护一个轮廓线,枚举轮廓线上的连通性。注意这个轮廓线是有拐点的,是一条曲线。
我们设 \(f_{i,j,s}\) 表示第 \(i\) 行连通性状态为 \(s\) 时,第 \(i\) 行第 \(j\) 列的那个格子是拐点。这时这个位置下方的连通性对应的是 \(s\) 的第 \(j-1\) 位,右方的连通性是 \(s\) 的 第 \(j\) 位。
需要特别注意的是,这个位置的上方的连通性是枚举的 \(j'=j-1\) 的其对应的状态(注意其二进制表示也是 \(s\))的第 \(j\) 位,左方的连通性是第 \(j-1\) 位。我们可以通过这个性质从以前的状态转移而来。
同时注意从上一行到当前行的状态继承时,需要特别将上一行的末尾的竖线的影响消除掉。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=15;
int n,m,f[N][N][1<<N],a[N][N],up;
void solve(){
cin>>n>>m;up=(1<<(m+1));for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j];
f[0][m][0]=1;
for(int i=1;i<=n;i++){
for(int s=0;s<up;s++){f[i][0][s<<1]=f[i-1][m][s];} //第i行的时候第0位是竖线,第i-1行的时候第m位是竖线。要把竖线的影响消除掉。
for(int j=1;j<=m;j++){
for(int s=0;s<up;s++){
int p=1<<(j-1),q=1<<j,x=s&p,y=s&q;//p是向下的插头,q是向右的插头
if(!a[i][j]) {if(!x&&!y) f[i][j][s]+=f[i][j-1][s];continue;}//这里j-1的s注意对应着当前位置的左和上没有插头
if(x&&y&&a[i+1][j]&&a[i][j+1]) {f[i][j][s]+=f[i][j-1][s^p^q];continue;}
if(!x&&y&&a[i][j+1]) {f[i][j][s]+=f[i][j-1][s^p^q]+f[i][j-1][s];continue;}
if(x&&!y&&a[i+1][j]) {f[i][j][s]+=f[i][j-1][s^p^q]+f[i][j-1][s];continue;}
if(!x&&!y) {f[i][j][s]+=f[i][j-1][s^p^q];continue;}
}
}
}
cout<<f[n][m][0]<<'\n';
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--) solve(),memset(f,0,sizeof(f));
return 0;
}

浙公网安备 33010602011771号