学习心得 - 动态规划 - 插头 DP

唉很久以前初学状态压缩 DP 留下的巨坑,填填。

什么是插头 DP

基于连通性状态压缩的动态规划问题。

%%% cdq 巨佬。

一般是求回路数量的。

什么是插头

如图。

一个蓝色方格有四个插头,就是指向相邻节点的插头。

我们求答案就是通过这些插头求的。

例如回路,我们就可以通过处理这些插头,让他们连成一个回路,来求解。

我们依据这些插头的存在求出答案。

怎么 DP

我们设 \(f_{i,j,s}\) 为当前 \(i,j\) 的状态 \(s\) 所能计算出的答案。

我们发现 \(n,m\leq 12\) 这些特别小的数。

通常考虑状压。

我们发现逐个转移的 DP 会把棋盘分为两半。

然后这个边界就是轮廓线。

如图。

这就是为什么插头 DP 又叫轮廓线 DP。

状态

性质:轮廓线两两不相交。

我们发现一个轮廓线上的插头有三个状态:入插头,出插头与无插头。

所以可以转换成三进制。

如图。

那如何编码呢?

我们发现三进制不好写,考虑转成 \(2^P\) 进制位运算。通常是 \(4\) 进制。

之后直接位运算即可,相信大家都会。

转移

转移方法有很多。

  1. 新建连通分量。
    • 无下插头与右插头。
  2. 合并连通分量。
    • 有下插头与右插头,让他们连通。
  3. 保持连通分量。
    • 只有下插头或右插头,做一个左插头或上插头让他合法。

优化

哈希优化,直接挂表记录相同状态。

预处理每一个插头状态。

DP 代码

#include<bits/stdc++.h>
using namespace std;
const int P=590027,N=6e5+10;
#define ll long long
ll f[2][N],nwans,res;
int sta[2][N],pr,nw,con[30],cur[2],ex,ey;
char ch;
int a[30][30],n,m;
int nwsta,upcon,lecon;
struct node{
	int pr,to;
}id[N];
int nxt[N],ncur;
void insert(int now,ll val){
	int add=now%P;
	for(int i=nxt[add];i;i=id[i].pr){
		if(sta[nw][id[i].to]==now){
			f[nw][id[i].to]+=val;
			return;
		}
	}
	cur[nw]++;
	sta[nw][cur[nw]]=now;
	f[nw][cur[nw]]=val;	
	id[++ncur].pr=nxt[add];
	id[  ncur].to=cur[nw];
	nxt[add]=ncur;
}
void dp(){
	pr=1,nw=0;
	cur[nw]=1;
	f[nw][1]=1;
	sta[nw][1]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=cur[nw];j++)sta[nw][j]<<=2;
		for(int j=1;j<=m;j++){
			ncur=0;
			memset(nxt,0,sizeof(nxt));
			swap(pr,nw);
			cur[nw]=0; 
			for(int k=1;k<=cur[pr];k++){
				nwsta=sta[pr][k];
				nwans=  f[pr][k];
				upcon=(nwsta>>con[j])%4;
				lecon=(nwsta>>con[j-1])%4;
				if(!a[i][j]){if((!upcon)&&(!lecon))insert(nwsta,nwans);}
				else if((!upcon)&&(!lecon)){if(a[i+1][j]&&a[i][j+1])insert(nwsta+(1<<con[j-1])+2*(1<<con[j]),nwans);}
				else if((!upcon)&&lecon){
					if(a[i+1][j])insert(nwsta,nwans);
					if(a[i][j+1])insert(nwsta-lecon*(1<<con[j-1])+lecon*(1<<con[j]),nwans);
				}else if(upcon&&(!lecon)){
					if(a[i][j+1])insert(nwsta,nwans);
					if(a[i+1][j])insert(nwsta-upcon*(1<<con[j])+upcon*(1<<con[j-1]),nwans);
				}else if(upcon==1&&lecon==1){
					int cnt=1;
					for(int l=j+1;l<=m;l++){
						if((nwsta>>con[l])%4==1)++cnt;
						else if((nwsta>>con[l])%4==2)--cnt;
						if(!cnt){
							insert(nwsta-(1<<con[l])-(1<<con[j])-(1<<con[j-1]),nwans);
							break;
						}
					}
				}else if(upcon==2&&lecon==2){
					int cnt=1;
					for(int l=j-2;l>=0;l--){
						if((nwsta>>con[l])%4==1)--cnt;
						else if((nwsta>>con[l])%4==2)++cnt;
						if(!cnt){
							insert(nwsta-2*(1<<con[j])+(1<<con[l])-2*(1<<con[j-1]),nwans);
							break;
						}
					}
				}else if(upcon==1&&lecon==2)insert(nwsta-2*(1<<con[j-1])-(1<<con[j]),nwans);
				else if(upcon==2&&lecon==1){if(i==ex&&j==ey)res+=nwans;}
			}
		}
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		cin>>ch;
		if(ch=='.')a[i][j]=1,ex=i,ey=j;
	}
	for(int j=1;j<=25;j++)con[j]=j<<1;
	dp();
	cout<<res;
	return 0;
}

https://www.luogu.com.cn/article/ld7682au

posted @ 2026-10-08 21:02  Ivan422  阅读(2)  评论(0)    收藏  举报