P5056 【模板】插头 dp

学了好久。。。

细节在代码中,建议看其他题解,然后就着我的代码理解qwq

点击查看代码
#include<bits/stdc++.h>
#include<unordered_map>
#define int long long
#define inf 1e18
#define inc 0xcfcfcfcf
#define N 15
#define M 500007
#define mod 1000000007
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
using namespace std;
int T=1,n,m,ex,ey;
int g[N][N];
unordered_map<int,int> f[2];
inline int Read()
{
	char ch=getchar();bool f=0;int x=0;
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	if(f==1)x=-x;return x;
}
int Get_s(int num,int pos){return (num<<(pos<<1));}//得到第pos位为num的四进制 
int Ctdp()
{
	int now=0;
	int mxn=(1<<((m+1)<<1))-1;//m+1位全为3的四进制码 
	f[now][0]=1; 
	f[now^1].clear();
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			int nxt=now^1;
			for(auto k:f[now])
			{
				int S=k.first,val=k.second;
				int ctl=(S>>((j-1)<<1))&3,ctr=(S>>(j<<1))&3;//ctl表示当前格子第一个插头,为1表示左插头,2为右插头,ctr类似 
				if(!g[i][j])//当前格子无法放置
				{
					if(!ctl&&!ctr)//若本来就没有插头,则原封不动转移 
						f[nxt][S]+=val;
					continue;
				}
				//下面为当前格子必须放置插头 
				if(!ctl&&!ctr)//若本来无插头进入 
				{
					if(g[i+1][j]&&g[i][j+1])//可以新建两个插头 
						f[nxt][S^Get_s(1,j-1)^Get_s(2,j)]+=val;
					continue;
				}
				if(!ctl&&ctr)//若只有第二个插头,可转移到下方和右方 
				{
					if(g[i+1][j])
					    f[nxt][S^Get_s(ctr,j-1)^Get_s(ctr,j)]+=val;
					if(g[i][j+1])
						f[nxt][S]+=val;
					continue;
				}
				if(ctl&&!ctr)//若只有第一个插头,也类似地可转移到下方和右方 
				{
					if(g[i][j+1])
					    f[nxt][S^Get_s(ctl,j-1)^Get_s(ctl,j)]+=val;
					if(g[i+1][j])
						f[nxt][S]+=val;
					continue;
				}
				//下面为两个插头都存在 
				if(ctl==ctr)//两个插头性质相同 
				{
					if(ctl==1)//如果都为左插头,则右边最近的一个右插头要变为左插头 
					{
						int cnt=1;
						for(int p=j+1;;++p)
						{
							int pos=(S>>(p<<1))&3;
							if(pos==1) ++cnt;
							if(pos==2) --cnt;
							if(cnt==0)
							{
								f[nxt][S^Get_s(ctl,j-1)^Get_s(ctr,j)^Get_s(2,p)^Get_s(1,p)]+=val;
								break;
							}	
						}
					}
					if(ctl==2)//如果都是右插头,则左边最近的左插头要变为右插头 
					{
						int cnt=1;
						for(int p=j-2;;--p)
						{
							int pos=(S>>(p<<1))&3;
							if(pos==2) ++cnt;
							if(pos==1) --cnt;
							if(cnt==0)
							{
								f[nxt][S^Get_s(ctl,j-1)^Get_s(ctr,j)^Get_s(2,p)^Get_s(1,p)]+=val;
								break;
							}	
						}
					}
					continue;
				}
				if(ctl==1&&ctr==2&&i==ex&&j==ey)//如果第一个为左插头,第二个为右插头,则形成回路,如果当前点是终点,则输出答案 
					return val;
				if(ctl==2&&ctr==1)//如果第一个为右插头,第二个为左插头,则形成一条线段,不影响其他线段,消除两个插头转移 
					f[nxt][S^Get_s(2,j-1)^Get_s(1,j)]+=val;
			}
			f[now].clear();
			now^=1;
		}
		for(auto k:f[now])//当转移到下一行的时候要注意所有状态应该四进制右移一位,然后截去最高的那位四进制,原理靠图理解 
		{
			int S=k.first,val=k.second;
			f[now^1][(S<<2)&mxn]+=val;
		}
		f[now].clear();
		now^=1;
	}
	return 0; 
}
bool Solve()
{
 	//freopen("test.in","r",stdin);
	n=Read(); m=Read();
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			char in;
			scanf(" %c",&in);
			if(in=='.')
				g[i][j]=1,ex=i,ey=j;//记录一下终点 
		}
	printf("%lld\n",Ctdp());
	return true;
}
signed main()
{
	//scanf("%lld",&T);
	while(T--)
		if(!Solve())
			printf("-1\n");
	return 0;
}
/*
-std=c++11
-std=c99
*/


posted @ 2023-03-29 16:17  模拟退火  阅读(14)  评论(0编辑  收藏  举报