插头dp

前置芝士

状压dp。

什么是插头dp

插头dp实际上是一种特殊的状压dp,特殊在他是一个格点一次转移的

插头dp怎么做

众所周知,dp分为三个部分,状态、转移和优化,以下皆以P5056【模板】插头 DP为例;

状态设置

在说清楚插头dp状态是什么之前,我们需要先知道插头和轮廓线是什么。

轮廓线

由于插头dp按照格点转移,已被转移的点和未被转移的点中间具有一条折线,这条线就是轮廓线

插头

其实每道题中插头的定义不尽相同,但是他们都有一个统一的特点,表示这条边相邻的两个方块的关系。
在这里,插头指的是这个点是否需要与他下/右边的相连,如图(图是我画的,比较丑)

其中蓝色表示一种方案,红色表示轮廓线,我们注意到红线上有的边被蓝线经过,有的没有,这些就是插头(本图共有5个插头)。
这些被经过的点的插头表示需要连接,没被经过则表示不必连接。

好的,现在我们已经知道了插头和轮廓线的基本定义,那么插头dp作为状压dp的一个分支,应该将什么标记进状态里呢?
不难想到应该状压轮廓线上的插头状态。
我们依然以上图为例,在上图中我们把需要连接定义为1,反之定义为0。那么轮廓线上的状态即为10111。
(友情提示,如果你是初学者的话,为了方便调试,建议将唯一一个横着的单独拎出来记成一维,具体见我后面放的代码)
但是如果单纯按照这种方式进行dp的话,不难发现可能出现最终连的是多个连通块的情况。
例如

我们现在的问题转化成了如何让避免这种状态出现。

可以很明显的感觉到在不改变状态的情况下这是极其困难的。
我们考虑改变状态:
注意到一个插头一定会与另外一个插头相连,且顺序固定,即不会出现两个插头在不消失的情况下换位置的情况。
我们可以用括号序来进行状压,若一个插头在与他相连的插头前面,则标记为1,否则标记为2,若没有插头则标为0


该图的轮廓线状态变为10212

至此状态部分彻底结束

转移

转移其实并不复杂,对于每个点,暴力分类其左边和上边的状态即可,这里举个例子:(配图太困难了,就挑了几个配)

如果当前枚举到的状态这个点上面和左边都是0,那么这个点一定会新建两个插头,将下插头标称1,右插头标称2

如果当前遇到的情况左0上不是0,这个点可以直接从上面向下连(下插头同上插头,右插头为0),或者在这个点拐个弯(右插头同上插头,下插头为0)

左非0上0同理

左1上1或左2上2,这两种情况都需要找到与其原来配对的插头并修改其权值。
例如
原来的轮廓线状态为01122,在连接完之后应将与上插头连接的改为1,即00012

左2上1,这种情况直接连即可

左1上2,这种情况回事一个连通块独立,不合法,只会在统计答案是用到

现在转移就都好了,但是如果这样写的话会TLE+MLE,这时候就要到第三步了

优化

优化运用的是hashtable优化dp,我们将状态的数字对一个质数取模,存在一个邻接表中,询问时直接遍历整个hashtable即可,听起来比较抽象,实际上非常好写,直接看代码就行了

#include<bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
#define N 15
#define M 600005
#define Mod 590027
using namespace std;
struct po{
	int k,val;
};
struct Hash_Table{  //hashtable优化
	int he[M],ne[M*5],cnt;
	po v[M*5];
	void init()
	{
		cnt=0;
		for(int i=0;i<Mod;i++)
			he[i]=0;
		return ;
	}
	void insert(int k,int val)
	{
		int K=k%Mod;
		for(int i=he[K];i;i=ne[i])
		{
			if(v[i].k==k)
			{
				v[i].val+=val;
				return ;
			}
		}
		++cnt;
		ne[cnt]=he[K];
		he[K]=cnt;
		v[cnt]=po{k,val};
	}
}g[2][3];//和文中所说一样,将横向插头拎出来单开一维
char s[15],num[15][15],Fl[15][15];
int Ans=0,P[15],T=0,n,m;
int getf(int k,int id)
{
	return (k/P[id-1])%3;
}
void Solve(int x)
{
	for(int i=1;i<=m;i++)
	{
		T^=1;
		g[T][0].init();
		g[T][1].init();
		g[T][2].init();
		if(num[x][i]==1)
		{
			for(int j=0;j<Mod;j++)
				for(int J=g[T^1][0].he[j];J;J=g[T^1][0].ne[J])
				{
					po I=g[T^1][0].v[J];
					if(getf(I.k,i)==0)
						g[T][0].insert(I.k,I.val);
				}
		}
		else  //大力分讨
		{
			for(int j=0;j<Mod;j++)
			{
				for(int J=g[T^1][0].he[j];J;J=g[T^1][0].ne[J])
				{
					po I=g[T^1][0].v[J];
					if(getf(I.k,i)==0)
					{
						g[T][2].insert(I.k+P[i-1],I.val);
					}
					if(getf(I.k,i)==1)
					{
						g[T][0].insert(I.k,I.val);
						g[T][1].insert(I.k-P[i-1],I.val);
					}
					if(getf(I.k,i)==2)
					{
						g[T][0].insert(I.k,I.val);
						g[T][2].insert(I.k-2*P[i-1],I.val);
					}
				}
				for(int J=g[T^1][1].he[j];J;J=g[T^1][1].ne[J])
				{
					po I=g[T^1][1].v[J];
					if(getf(I.k,i)==0)
					{
						g[T][1].insert(I.k,I.val);
						g[T][0].insert(I.k+P[i-1],I.val);
					}
					if(getf(I.k,i)==1)
					{
						int Cnt=1,k=i;
						while(Cnt!=0)//找到配对的另一个
						{
							k++; 
							if(getf(I.k,k)==1)
								Cnt++;
							else if(getf(I.k,k)==2)
								Cnt--;
						}
						g[T][0].insert(I.k-P[i-1]-P[k-1],I.val);
					}
					if(getf(I.k,i)==2)//这里就是文中说的不合法的地方,只在统计答案是用到
					{
						if(Fl[x][i])//表示最后一个非障碍节点
							if(I.k==2*P[i-1])
								Ans+=I.val;
					}
				}
				for(int J=g[T^1][2].he[j];J;J=g[T^1][2].ne[J])
				{
					po I=g[T^1][2].v[J];
					if(getf(I.k,i)==0)
					{
						g[T][2].insert(I.k,I.val);
						g[T][0].insert(I.k+2*P[i-1],I.val);
					}
					if(getf(I.k,i)==1)
					{
						g[T][0].insert(I.k-P[i-1],I.val);
					}
					if(getf(I.k,i)==2)
					{
						int Cnt=1,k=i;
						while(Cnt!=0)
						{
							k--;
							if(getf(I.k,k)==1)
								Cnt--;
							else if(getf(I.k,k)==2)
								Cnt++;
						}
						g[T][0].insert(I.k-2*P[i-1]+P[k-1],I.val);
					}
				}
			}
		}
	}
	return ;
}
void solve()
{
	g[0][0].insert(0,1);
	for(int i=1;i<=n;i++)
	{
		Solve(i);
		g[T][1].init();
		g[T][2].init();
	}
	return ;
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		for(int j=1;j<=m;j++)
			num[i][j]=s[j]=='*';
	}
	int x=n,y=m;
	while(1) //寻找最后一个非障碍节点
	{
		if(num[x][y]==1)
		{
			y--;
			if(y==0)
			{
				x--;
				y=m;
			}
		}
		else
		{
			Fl[x][y]=1;
			break;
		}
	}
	P[0]=1;
	for(int i=1;i<=m;i++)
		P[i]=P[i-1]*3;
	solve();
	printf("%lld ",Ans);
	return 0;
}
posted @   zyb_123  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示