插头DP

讲解

https://www.luogu.com.cn/problemnew/solution/P5056

https://blog.csdn.net/litble/article/details/79369147

https://blog.csdn.net/zhangjianjunab/article/details/80987531

例1、题目大意:一个网格图中有若干障碍格子,求其他格子的哈密尔顿回路总数

只能有一个回路,有多少种可能

//我觉得这个写法比较好理解
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int mod=200000;//hash表大小,因为hash表有压缩功能,所以一般100万就够了
int map[50][50]={0};
int n,m,ex=-1,ey=-1; //最后一个非障碍格子的坐标
char ss[210];
LL ans=0;
int now,php;

struct node{
	int hash[mod]; //压缩状态 
	int key[mod];   //记录原本的数值 
	int size;    // 记录存了多少次数 
	LL num[mod];  // 记录当前数值代表了多少状态
	void mem(){
		memset(hash,-1,sizeof(hash));
		size=0;  //初始化函数 
	} 
	void add(int S,int sum){
		int s=S%mod;
		while(hash[s]!=-1&&key[hash[s]]!=S){
			s++;
			s%=mod;
		}
		//判断重复,保证下一次同样一个数也可以到达这个hash值
		if(hash[s]==-1){
			hash[s]=++size;   //压缩状态 
			key[size]=S;      //存储原本的数值 
			num[size]=sum;    //代表了多少状态 
		}  //新建一个hash格
		else num[hash[s]]+=sum; //有的话直接加 
	}
}dp[2]; //滚动数组 

int set(int s,int p){
	return (s>>((p-1)*2))&3;  //取出第p个位置的数 
}
void change(int &s,int p,int v){
	s^=set(s,p)<<((p-1)*2); 
	s^=(v&3)<<((p-1)*2);   //改变第p位上的数位v 
}

void wrok(){
	LL sum=0;
	now=0,php=1;
	dp[now].mem();
	dp[now].add(0,1);  //初始化滚动型DP数组
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			swap(now,php); //使用滚动数组
			dp[now].mem();
			for(int k=1;k<=dp[php].size;k++){
				int s=dp[php].key[k];
				sum=dp[php].num[k];
				int  q=set(s,j);   //右插头 
				int  p=set(s,j+1); //下插头
			if(map[i][j]==0){  //障碍格子 ,不能有插头 
				if(p==0&&q==0) dp[now].add(s,sum);
				continue; 
			} 
			if(q==0&&p==0){  //|一 没人指我,只好自己建一个插头 
				if(map[i+1][j]==1&&map[i][j+1]==1){  //判断是不是障碍格子 
					change(s,j,1);  //
					change(s,j+1,2);//从这个格子建两个插头
					dp[now].add(s,sum);
				}
			}
			else if(q>0&&p==0){ //两种方式 
				if(map[i+1][j]==1) dp[now].add(s,sum); //不会改变
				if(map[i][j+1]==1){ //把插头指向右边,继承下去 
					change(s,j,0);
					change(s,j+1,q);  //相当于交换这两列
					dp[now].add(s,sum); 
				} 
			}
			else if(q==0&&p>0){  //跟上面一样,两种方式 
				if(map[i][j+1]==1) dp[now].add(s,sum);
				if(map[i+1][j]==1){
					change(s,j,p);
					change(s,j+1,0);
					dp[now].add(s,sum);
				}
			}
			else if(q==1&&p==1){
				int find=1; //算上p为1!很重要。
				for(int tt=j+2;tt<=m;tt++){ //找到与p相对的右插头并修改为这一大块插头的左插头
					int vs=set(s,tt);
					if(vs==1) find++;
					else if(vs==2) find--;////为什么可以?因为中间罩住的插头不会出去这个大插头的范围,所以只有碰到与p成对的插头才会清零
					if(find==0){
						change(s,j,0);
						change(s,j+1,0);
						change(s,tt,1);
						dp[now].add(s,sum);
						break;
					}
				} 
			}
			else if(q==2&&p==2){
				int find=1;
				for(int tt=j-1;tt>=1;tt--){ ///找到与q相对的左插头并修改为这一大块插头的右插头
					int vs=set(s,tt);
					if(vs==2) find++;
					else if(vs==1) find--;
					if(find==0){
						change(s,j,0);
						change(s,j+1,0);
						change(s,tt,2);
						dp[now].add(s,sum);
						break;
					}
				}
			}
			else if(q==2&&p==1){  //这样配对的插头刚好是我们想要的 
				change(s,j,0);
				change(s,j+1,0); //直接抵消清零就可以了
				dp[now].add(s,sum); 
			}
			else if(q==1&&p==2){
				if(ex==i&&ey==j) ans+=sum;//得到答案 
			}
			}
		}
		for(int j=1;j<=dp[now].size;j++) dp[now].key[j]<<=2; 
	} 
} 

int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",ss+1);
		for(int j=1;j<=m;j++){
			if(ss[j]=='.'){
				map[i][j]=1;
				ex=i;
				ey=j;
			}
		}
	}
	if(ex==-1) {
		printf("0
");
		return 0;
	}
	wrok();
	printf("%lld
",ans);
	return 0;
}

  例2、有多个回路,有多少种可能

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n,m;
int bin[15],mp[15][15];
LL f[12][12][(1<<12)]; 
int t;
int l=1;
//这道题的回路数目不限,所以只需要记录有没有插头的状态,
int main(){
	scanf("%d",&t);
	bin[0]=1; //预处理 
	for(int i=1;i<=12;i++) bin[i]=bin[i-1]<<1;
	while(t--){
		scanf("%d %d",&n,&m);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=m;j++) scanf("%d",&mp[i][j]);
		}
		memset(f,0,sizeof(f)); //每次都清零
		f[0][m][0]=1;
		int lim=bin[m+1]-1; //状态数
		for(int i=1;i<=n;i++){
			for(int zt=0;zt<=lim;zt++) f[i][0][zt<<1]=f[i-1][m][zt];  ///!!!!
				for(int j=1;j<=m;j++)
				for(int zt=0;zt<=lim;zt++){
					int b1=zt&bin[j-1];
					int b2=zt&bin[j];
					LL num=f[i][j-1][zt];
				if(!mp[i][j]){
					if(!b1&&!b2) f[i][j][zt]+=num;
					continue;
				}
				if(!b1&&!b2){
					if(mp[i][j+1]&&mp[i+1][j]) f[i][j][zt+bin[j-1]+bin[j]]+=num;
					
				}
				else if(!b1&&b2){
					if(mp[i][j+1]) f[i][j][zt]+=num;
					if(mp[i+1][j]) f[i][j][zt-bin[j]+bin[j-1]]+=num;
				}
				else if(b1&&!b2){
					if(mp[i][j+1]) f[i][j][zt-bin[j-1]+bin[j]]+=num;
					if(mp[i+1][j]) f[i][j][zt]+=num;
				}
				else f[i][j][zt-bin[j-1]-bin[j]]+=num;
				} 
				
			}
		 printf("Case %d: There are %lld ways to eat the trees.
",l++,f[n][m][0]);
	}
	
	return 0;
} 

  

原文地址:https://www.cnblogs.com/shirlybaby/p/12313357.html