论插头dp的写法

题目:https://www.luogu.com.cn/problem/P5056

毕竟现在还不会写,先放几个大佬的代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=15,M=299989,STA=300000+10;
int n,m,mp[N][N],ex,ey;
int head[STA],nxt[STA];
LL tot[2],f[2][STA],t[2][STA],ans;
int now,las;
void insert(int zt,LL num){
	int x=zt%M+1;
	for(int i=head[x];i;i=nxt[i]){
		if(t[now][i]==zt){
			f[now][i]+=num;
			return;
		}
	}
	nxt[++tot[now]]=head[x];head[x]=tot[now];
	t[now][tot[now]]=zt;f[now][tot[now]]=num;
}
void DP(){
	tot[now]=1;f[now][1]=1;t[now][1]=0;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=tot[now];++j)t[now][j]<<=2;
		for(int j=1;j<=m;++j){
			las=now;now^=1;
			memset(head,0,sizeof(head));tot[now]=0;
			for(int k=1;k<=tot[las];++k){
				int zt=t[las][k];
				int p1=(zt>>(j*2-2))%4,p2=(zt>>(j*2))%4;
				LL num=f[las][k];
				if(!mp[i][j]){
					if(!p1&&!p2)insert(zt,num);
				}
				else if(!p1&&!p2){
					if(mp[i+1][j]&&mp[i][j+1])insert(zt+(9<<(j*2-2)),num);
				}
				else if(!p1&&p2){
					if(mp[i][j+1])insert(zt,num);
					if(mp[i+1][j])insert(zt+(p2<<(j*2-2))-(p2<<(j*2)),num);
				}
				else if(p1&&!p2){
					if(mp[i+1][j])insert(zt,num);
					if(mp[i][j+1])insert(zt-(p1<<(j*2-2))+(p1<<(j*2)),num);
				}
				else if(p1==1&&p2==1){
					int cnt=1;
					for(int d=j+1;d<=m;++d){
						if((zt>>(d*2))%4==1)++cnt;
						if((zt>>(d*2))%4==2)--cnt;
						if(!cnt){
							insert(zt-(5<<(j*2-2))-(1<<(d*2)),num);
							break;
						}
					}
				}
				else if(p1==2&&p2==2){
					int cnt=1;
					for(int d=j-2;d>=0;--d){
						if((zt>>(d*2))%4==1)--cnt;
						if((zt>>(d*2))%4==2)++cnt;
						if(!cnt){
							insert(zt-(10<<(j*2-2))+(1<<(d*2)),num);
							break;
						}
					}
				}
				else if(p1==2&&p2==1)insert(zt-(6<<(j*2-2)),num);
				else if(i==ex&&j==ey)ans+=num;
			}
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	char ch;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			cin>>ch;
			if(ch=='.')mp[i][j]=1,ex=i,ey=j;
			else mp[i][j]=0;
		}
	}
	DP();
	printf("%lld",ans);
	return 0;
}
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
#define Fast_cinios::sync_with_stdio(false),cin.tie();
#define For(i,a,b) for(registerinti=a;i<=b;i++)
#define Forr(i,a,b) for(registerinti=a;i>=b;i--)
#define DEBUG(x) cerr<<"DEBUG"<<x<<">>>"<<endl;
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
template<typename _T>
inline void read(_T&f){
	f=0;_T fu=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')fu=-1;c=getchar();}
	while(c>='0'&&c<='9'){f=(f<<3)+(f<<1)+(c&15);c=getchar();}
	f*=fu;
}
template<typename T>
void print(T x){
	if(x<0)putchar('-'),x=-x;
	if(x<10)putchar(x+48);
	else print(x/10),putchar(x%10+48);
}
template<typename T>
	void print(T x,char t){
	print(x);putchar(t);
}
const int mod=15527,N=mod+5;
ll f[2][N],ans;
int a[15][15],tot[2],head[N],nxt[N],v[2][N],bin[15],n,m,end1,end2,now;
/*
ans->答案
now->滚动数组第一维
f->滚动数组dp,f[now][i]表示的是第i个状态的dp值,第i个状态是hash得来
a->存地图
tot->存某一层的状态数,前向星hash表
nxt&head->hash表
v->状态的4进制数表示,状态是从左到右,从低位到高位记录的,第一个插头是第一个四进制位
bin->4的若干次方,便于从状态中提取第i位的插头
end1&end2->最后一个合法点的坐标,方便统计答案
*/
//ins->对状态进行hash
void ins(int zt,ll val){
	int u=zt%mod;
	for(register int i=head[u];i;i=nxt[i])
		if(v[now][i]==zt){
			f[now][i]+=val;
			return;
		}
	tot[now]++;nxt[tot[now]]=head[u];v[now][tot[now]]=zt;
	head[u]=tot[now];f[now][tot[now]]=val;
}
void sol(){
	tot[now]=1;f[now][1]=1;v[now][1]=0;
	for(register int i=1;i<=n;i++){
		for(register int j=1;j<=tot[now];j++)v[now][j]<<=2;
		for(register int j=1;j<=m;j++){
			now^=1;memset(head,0,sizeof(head));tot[now]=0;//清空hash表
			//枚举从上一层dp中的哪一个状态转移过来
			for(register int k=1;k<=tot[now^1];k++){
				int zt=v[now^1][k];ll val=f[now^1][k];
				//提取关键的两个插头的状态,即(i,j-1)到(i,j)的插头和(i-1,j)到(i,j)的插头
				int t1=(zt>>((j<<1)-2))&3,t2=(zt>>(j<<1))&3;
				//				fprintf(stderr,"zt=%d,t1=%d,t2=%d\n",zt,t1,t2);
				//情况1,(i,j)这个点不能走
				if(a[i][j]==0){
					//不能有过来的插头,关键的两个插头都为0,即两个方格都没有过来的边,才能转移
					if(t1==0&&t2==0)ins(zt,val);
					continue;
				}
				//(i,j)一定要走
				//情况2,两个关键插头都为0
					if(t1==0&&t2==0){
					//因为(i,j)一定要走,所以放上一个(i,j)到(i+1,j)且(i,j)到(i,j+1)的插头
					//需要判断(i+1,j)和(i,j+1)是否能走
					if(a[i+1][j]==1&&a[i][j+1]==1)ins(zt^bin[j-1]^(bin[j]<<1),val);
				}else if(t1==0&&t2!=0){
					if(a[i][j+1]==1)ins(zt,val);
					if(a[i+1][j]==1)ins(zt^(bin[j-1]*t2)^(bin[j]*t2),val);
				}else if(t1!=0&&t2==0){
					if(a[i][j+1]==1)ins(zt^(bin[j-1]*t1)^(bin[j]*t1),val);
					if(a[i+1][j]==1)ins(zt,val);
				}else if(t1==1&&t2==1){
					//需要找到一个2插头来匹配,for循环暴力找,但是中间的12括号要匹配
					int nowv=1;//1插头的个数
					//这里题解写的是t<=m,但画了图发现好像t<m就可以了,希望以后不要错
					for(register int t=j+1;t<m;t++){
						int t3=(zt>>(t<<1))&3;
						if(t3==1)nowv++;
						if(t3==2)nowv--;
						if(!nowv){ins(zt^(bin[t]*3)^bin[j-1]^bin[j],val);break;}
					}
				}else if(t1==2&&t2==2){
					int nowv=1;//2插头的个数
					//这里题解写的是t>=0,但根据同理可得发现好像t>0就可以了,希望以后不要错
					for(register int t=j-2;t>0;t--){
					int t3=(zt>>(t<<1))&3;
					if(t3==1)nowv--;
					if(t3==2)nowv++;
					if(!nowv){ins(zt^(bin[t]*3)^(bin[j-1]<<1)^(bin[j]<<1),val);break;}
					}
				}else if(t1==2&&t2==1)ins(zt^(bin[j-1]<<1)^bin[j],val);
				else if(end1==i&&end2==j)ans+=val;
			}
		}
	}
}
int main(){
	read(n);read(m);
	for(register int i=1;i<=n;i++){
		for(register int j=1;j<=m;j++){
		char c=getchar();
		while(c!='.'&&c!='*')c=getchar();
		if(c=='.')a[i][j]=1,end1=i,end2=j;
		}
	}
	bin[0]=1;
	for(register int i=1;i<=m;i++)bin[i]=bin[i-1]<<2;
	sol();print(ans,'\n');
	return 0;
}
/*

插头的状态有0,1,2三种,其中0表示这个地方没有插头,1和2是根据插头的x坐标进行区分的,在左边的是1,右边的是2
参考:https://blog.csdn.net/litble/article/details/79369147

*/

猜你喜欢

转载自www.cnblogs.com/nakiri-ayame-suki/p/13386265.html