插头DP

入坑题
参考博客

适用范围:数据范围极小,与连通性有关,网格图。

转移时用括号序来表示插头情况,然后分类讨论一下当前格子的左轮廓与上轮廓,也就是看有没有右插头与下插头,主要分为三大类:
①两个插头都没有
②只有一个插头
③两个插头都有

再分别讨论一下插头是 “ ( ” “(” (还是 “ ) ” “)” )即可。

当一层转移完时,要从这一层的最后一个格子跳到下一层的第一个格子。那么当前所有的合法状态的四进制表示的最高位一定为0,因为最右边那条边一定是没有插头的。而下一层的最低位也一定为0,因为下一层第一个格子的左边一定没有插头。

所以把当前这一层的合法的状态左移一位(四进制就是乘4),就可以得到下一层的初始状态了。

两个插头都存在的时候,可能会把一些边联通,所以要找到被连通的边把它对应的插头改掉,并且把连通的地方的插头删掉。

其他情况都比较好讨论,在参考博客里有详细说明 o r z orz orz

由于一层中有很多无用状态,所以用一个数据结构来存储合法状态,每次就从上一层的合法状态集合中取出一个状态来转移。所以需要支持以下操作:
①遍历所有元素
②支持插入一个关键字。(添加一个合法状态)
③支持修改一个关键字的值。
(因为可能有多个不同状态转移到相同状态。)

采用哈希表。 s e t set set应该也可以吧。
a d d ( u , s t a , v a l ) add(u,sta,val) add(u,sta,val)表示在关键字 u u u中插入一个状态为 s t a sta sta,值为 v a l val val的元素。然后就没了。

d w n dwn dwn r g t rgt rgt分别提取的是上一个状态的上左两条边。
现在要转移到下右两条边。

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const
using namespace std;
cs int base=4,M=14,mod=599087,N=6e5+10;
int n,m,cur,pre,last_x,last_y,mp[M][M],state[2][N],power[M];ll ans,dp[2][N];
namespace Hash_Table{
	int Head[N],Next[N],cnt[2]={0};
	inline void add(int u,int sta,ll val){
		Next[++cnt[cur]]=Head[u],Head[u]=cnt[cur];
		state[cur][cnt[cur]]=sta,dp[cur][cnt[cur]]=val;
	}
	inline void insert(int sta,ll val){
		int u=sta%mod+1;
		for(int re i=Head[u];i;i=Next[i])
			if(state[cur][i]==sta){dp[cur][i]+=val;return;}
		add(u,sta,val);
	}
}
using namespace Hash_Table;
inline int get(){
	char ch=getchar();
	while(ch!='.'&&ch!='*') ch=getchar();
	return ch=='.';
}
inline void solve(){
    insert(0,1);
	for(int re i=1;i<=n;++i){
		for(int re j=1;j<=cnt[cur];++j) state[cur][j]<<=2;
		for(int re j=1;j<=m;++j){
			memset(Head,0,sizeof Head);
			pre=cur,cur^=1,cnt[cur]=0;
			int re nowsta,dwn,rgt;ll re nowans;
			for(int re k=1;k<=cnt[pre];++k){
				nowsta=state[pre][k],nowans=dp[pre][k];
				dwn=(nowsta>>(j<<1))%base,rgt=(nowsta>>((j-1)<<1))%base;
				if(!mp[i][j]){
					if(!(dwn+rgt)) insert(nowsta,nowans);
				}else if(!(dwn+rgt)){//有障碍 
					if(mp[i+1][j]&&mp[i][j+1]) insert(nowsta+power[j-1]+power[j]*2,nowans);
				}else if((!dwn)&&rgt){
					if(mp[i+1][j]) insert(nowsta,nowans);//向下走不改变括号序。
					if(mp[i][j+1]) insert(nowsta-power[j-1]*rgt+power[j]*rgt,nowans);//向右走相当于把末端在括号序上往右移动一位。
				}else if(dwn&&(!rgt)){
					if(mp[i][j+1]) insert(nowsta,nowans);//向右走不改变括号序。 
					if(mp[i+1][j]) insert(nowsta-power[j]*dwn+power[j-1]*dwn,nowans);//与上面相反。 
				}else if(dwn==1&&rgt==1){
					int re count=1;//找到连出去的第一个2,改为1,并把交接处的插头删掉 
					for(int re l=j+1;l<=m;++l){
						if((nowsta>>(l<<1))%4==1) ++count;
						else if((nowsta>>(l<<1))%4==2) --count;
						if(!count){insert(nowsta-power[l]-power[j]-power[j-1],nowans);break;}
					}
				}else if(dwn==2&&rgt==2){
					int re count=1;//找到连出去的第一个1,改为2,并把交接处的插头删掉  
					for(int re l=j-2;l>=0;--l){
						if((nowsta>>(l<<1))%4==1) --count;
						else if((nowsta>>(l<<1))%4==2) ++count;
						if(!count){insert(nowsta+power[l]-power[j]*2-power[j-1]*2,nowans);break;}
					}
				}else if(dwn==1&&rgt==2){
					insert(nowsta-power[j]-power[j-1]*2,nowans);
				}else if(dwn==2&&rgt==1) if(i==last_x&&j==last_y) ans+=nowans;
			}
		}
	}
}
int main(){
//	freopen("5056.in","r",stdin);
	scanf("%d%d",&n,&m),power[0]=1;
	for(int i=1;i<M;++i) power[i]=power[i-1]<<2;
    for(int re i=1;i<=n;++i)
		for(int re j=1;j<=m;++j)
			if(mp[i][j]=get()) last_x=i,last_y=j;
    solve(),printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值