P3190 [HNOI2007]神奇游乐园

发布时间 2023-03-31 17:41:31作者: 模拟退火

P3190 [HNOI2007]神奇游乐园

\(unordered\_map\)有个坑,写在了下面这个博客

https://www.luogu.com.cn/blog/zhouzhuo/gei-yong-unorderedmap-di-hou-ren-ti-gong-dai-ma

再贴一下代码吧

点击查看代码
#include<bits/stdc++.h>
#include<unordered_map>
#define int long long
#define inf 1e18
#define inc 0xcfcfcfcf
#define N 107
#define M 17
#define mod 1000000007
//#pragma GCC optimize(2)
//#pragma GCC optimize(3)
using namespace std;
int T=1,n,m,ex,ey;
int ans=-inf; 
int g[N][M];
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的四进制 
void Insert(int pos,int S,int val)
{
	if(f[pos].find(S)==f[pos].end())
		f[pos][S]=val;
	else
		f[pos][S]=max(f[pos][S],val);
}
void Ctdp()
{
	int now=0;
	int mxn=(1<<((m+1)<<1))-1;//m+1位全为3的四进制码,方便后面截去m+1位之后的数 
	f[now][0]=0; 
	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])
			{
			//printf("(%lld,%lld)qwq\n",i,j);
				int S=k.first,val=k.second;  
				int ctl=(S>>((j-1)<<1))&3,ctr=(S>>(j<<1))&3;//ctl表示当前格子第一个插头,为1表示左插头,2为右插头,ctr类似
				//下面为当前格子必须放置插头
				if(!ctl&&!ctr)//若本来无插头进入 
				{
					Insert(nxt,S,val);
					if(j!=m&&i!=n)//可以新建两个插头 
						Insert(nxt,S^Get_s(1,j-1)^Get_s(2,j),val+g[i][j]);
				}
				if(!ctl&&ctr)//若只有第二个插头,可转移到下方和右方 
				{
					if(i!=n)
					    Insert(nxt,S^Get_s(ctr,j-1)^Get_s(ctr,j),val+g[i][j]);
					if(j!=m)
						Insert(nxt,S,val+g[i][j]);
				}
				if(ctl&&!ctr)//若只有第一个插头,也类似地可转移到下方和右方 
				{
					if(j!=m)
					    Insert(nxt,S^Get_s(ctl,j-1)^Get_s(ctl,j),val+g[i][j]);
					if(i!=n)
						Insert(nxt,S,val+g[i][j]);
				}
				//下面为两个插头都存在 
				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)
							{
								Insert(nxt,S^Get_s(ctl,j-1)^Get_s(ctr,j)^Get_s(2,p)^Get_s(1,p),val+g[i][j]);
								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)
							{
								Insert(nxt,S^Get_s(ctl,j-1)^Get_s(ctr,j)^Get_s(2,p)^Get_s(1,p),val+g[i][j]);
								break;
							}	
						}
					}
				}
				if(ctl==1&&ctr==2)//如果第一个为左插头,第二个为右插头,则形成回路
					ans=max(ans,val+g[i][j]);
				if(ctl==2&&ctr==1)//如果第一个为右插头,第二个为左插头,则形成一条线段,不影响其他线段,消除两个插头转移 
					Insert(nxt,S^Get_s(2,j-1)^Get_s(1,j),val+g[i][j]);
				
			}
			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;
	}
}
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)
			g[i][j]=Read();		
	Ctdp();
	printf("%lld\n",ans);
	return true;
}
signed main()
{
	//T=Read();
	while(T--)
		if(!Solve())
			printf("-1\n");
	return 0;
}
/*
-std=c++11
-std=c99
*/