P5074 Eat the Trees

P5074 Eat the Trees

套着板子写,写了份四进制和二进制的

四进制中与板子不同的是其实插头不需要区分左右了,之前左右匹配的情况可以消去插头继续转移

注意特判全为0情况

四进制代码:

点击查看代码
#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的四进制码 ,方便后面截去m+1位之后的数 
	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)//如果第一个为左插头,第二个为右插头,则形成回路,如果当前点是终点,则输出答案 
				{
					if(i==ex&&j==ey)
						return val;
					f[nxt][S^Get_s(ctl,j-1)^Get_s(ctr,j)]+=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();
	memset(g,0,sizeof(g));
	f[0].clear();
	f[1].clear();
	ex=-1,ey=-1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			g[i][j]=Read();
			if(g[i][j])
				ex=i,ey=j;//记录一下终点 
		}
	if(ex==-1&&ey==-1)
	{
		printf("1\n");
		return 1;
	 } 	
	printf("%lld\n",Ctdp());
	return true;
}
signed main()
{
	T=Read();
	while(T--)
		if(!Solve())
			printf("-1\n");
	return 0;
}
/*
-std=c++11
-std=c99
*/


二进制代码:

点击查看代码
#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);}
int Ctdp()
{
	int now=0;
	int mxn=(1<<(m+1))-1;
	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,ctr=(S>>j)&1;//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(1,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(i==ex&&j==ey)
						return val;
					f[nxt][S^Get_s(ctl,j-1)^Get_s(ctr,j)]+=val;
				}	
			}
			f[now].clear();
			now^=1;
		}
		for(auto k:f[now])//当转移到下一行的时候要注意所有状态应该四进制右移一位,然后截去最高的那位四进制,原理靠图理解 
		{
			int S=k.first,val=k.second;
			f[now^1][(S<<1)&mxn]+=val;
		}
		f[now].clear();
		now^=1;
	}
	return 0; 
}
bool Solve()
{
 	//freopen("test.in","r",stdin);
	n=Read(); m=Read();
	memset(g,0,sizeof(g));
	f[0].clear();
	f[1].clear();
	ex=-1,ey=-1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			g[i][j]=Read();
			if(g[i][j])
				ex=i,ey=j;//记录一下终点 
		}
	if(ex==-1&&ey==-1)
	{
		printf("1\n");
		return 1;
	 } 
	printf("%lld\n",Ctdp());
	return true;
}
signed main()
{
	T=Read();
	while(T--)
		if(!Solve())
			printf("-1\n");
	return 0;
}
/*
-std=c++11
-std=c99
*/


posted @ 2023-03-31 18:36  模拟退火  阅读(12)  评论(0编辑  收藏  举报