萌新刚学OI,插头DP只有30分,求助大佬
查看原帖
萌新刚学OI,插头DP只有30分,求助大佬
169423
洁咪楼主2021/12/27 12:40

调了一下午,人已经麻了


#include<bits/stdc++.h>//plugdp by shaymin
using namespace std;
typedef long long ll;
const int MOD=40001;

int head[600000],nxt[600000];//HASH表 

int n,m,area[13][13]/*地图*/,endx,endy/*结束点坐标*/,pre=1,now;

ll ans,f[2][600000],state[2][600000]/*状态*/,cnt[2]/*状态数*/; 

int bit[14];//便于提取插头,实际上bit[i] = 4**i

int get(ll s,int num){//获取 s 状态的第 num 位
	return (s>>(num*2)) & ((1<<2)-1);//建议手模 (注:这里是4进制表示的!) 
}
void change(ll &s,int num,int v){//改变 s 状态的第 num 位为 v
	s^=get(s,num)<<(num*2);//第 num 位改为0 
	s|=v<<(num*2);//第 num 位改为v 
}
void hashin(ll s,ll num);//存入HASH表 
void dp();
int main(){
//	freopen("plugdp.in","r",stdin);
//	freopen("plugdp.out","w",stdout);
	scanf("%d%d",&n,&m);
	char s[15];
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		for(int j=1;j<=m;j++){
           if(s[j]=='.'){
               area[i][j]=1;
               endx=i; endy=j;//记录终点位置 
           }
       }
	}
//	bit[0]=1;
//   	for(int i=1;i<=13;i++)
//       bit[i]=bit[i-1]<<2;
       
    //读入及初始化↑
    
    dp();
    
    printf("%lld",ans);

	return 0;
}
void dp(){
	f[now][1]=1; cnt[now]=1; state[now][1]=0;//DP的初始化 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=cnt[now];j++) state[now][j]<<=2;//换行操作 因为是4进制表示所以要左移两位
		 
		for(int j=1;j<=m;j++){//枚举每一个格子 
			memset(head,0,sizeof(head));//清空HASH表 
			swap(now,pre);//交换pre与now (滚动数组) 
			cnt[now]=0;//清空状态
//			cout<<cnt[now]<<" "<<cnt[pre]<<endl;
			for(int k=1;k<=cnt[pre];k++){
//				cout<<"tty ak ioi"<<endl;
				ll s=state[pre][k],num=f[pre][k];//提取上一个格子的状态、dp数组的值 
				ll p1=get(s,j-1),p2=get(s,j);//获取左边格子的插头、上面格子的插头
				if(area[i][j]==0){ 
//				cout<<"tty ak ioi"<<endl;
					if(p1==0&&p2==0)
						hashin(s,num);
				}//有障碍,此时若无上插头、左插头,直接存之前状态进HASH表 
				else if(p1==0&&p2==0){
				 	ll saves=s;
				 	change(s,j-1,1); change(s,j,2);
				 	hashin(s,num);
				 	s=saves;
				}//无左插头、上插头 此时直接添加下插头、右插头(即新开一个连通分量)
				else if((p1==0&&p2!=0)||(p2==0&&p1!=0)){ 
					if(area[i+(p1>0)][j+(p2>0)])//这些情况时不需要改状态 
						hashin(s,num); 
					if(area[i+(p2>0)][j+(p1>0)]){//这些情况时需要改状态 
						ll saves=s;
				 		change(s,j-1,p2); change(s,j,p1);
				 		hashin(s,num);
				 		s=saves;
					}
				}//无左插头或者无上插头 
				
				//需要合并连通分量的情况↓ (各状态的图)
				else if(p1==1&&p2==1){//左插头是左括号,上插头也是左括号 
					int flag=1;
					for(int q=j+1;q<=m;q++){//寻找p2匹配的右括号 
						int nows=get(s,q); 
						if(nows==1) flag++;
						else if(nows==2) flag--;//括号匹配操作 
						if(flag==0){
							ll saves=s;
				 			change(s,j-1,0); change(s,j,0); change(s,q,1);//改变括号 
				 			hashin(s,num);
				 			s=saves;
				 			break; 
						}
					}
				}
				else if(p1==2&&p2==2){//左插头是右括号,上插头也是右括号 
					int flag=1;
					for(int q=j-2;q>=1;q--){//寻找p1匹配的左括号 
						int nows=get(s,q); 
						if(nows==2) flag++;
						else if(nows==1) flag--;
						if(flag==0){
							ll saves=s;
				 			change(s,j-1,0); change(s,j,0); change(s,q,2);
				 			hashin(s,num);
				 			s=saves;
				 			break; 
						}
					}
				}
				else if(p1==2&&p2==1){//左插头是右括号,上插头是左括号 
					ll saves=s;
				 	change(s,j-1,0); change(s,j,0);
				 	hashin(s,num);
				 	s=saves;
				} 
				else if(p1==1&&p2==2){//左插头是左括号,上插头是右括号,即最终匹配完成 
					if(i==endx&&j==endy)//此时若是终点则统计答	案数 
						ans+=num; 
				}
			} 
//			for(int i=1;i<=cnt[now];i++){
//				cout<<f[now][state[now][i]]<<" ";
//			}
				
//			cout<<endl;
		}
	} 
} 
void hashin(ll s,ll num){
	ll u=s%MOD+1;
	for(int i=head[u];i;i=nxt[i]){//查找对应状态,累加方案数 
		if(state[now][i]==s){
			f[now][i]+=num;
			return ;
		}
	}
	//如果目前HASH表内还没有该状态,则存入↓
	
	nxt[++cnt[now]]=head[u];
	head[u]=cnt[now]; 
	state[now][cnt[now]]=s;
	f[now][cnt[now]]=num;
}
2021/12/27 12:40
加载中...