插头dp学习总结

定义

一类特殊的状态压缩dp,又称轮廓线dp

作用

通常用于解决二维空间的状态压缩问题,且每个位置的取值只与临近的几个位置有关,适用于超小数据范围,网格图,连通性等问题。

模板

#include<bits/stdc++.h>
#define md 312251
#define mxn 15
using namespace std;
int mp[mxn][mxn],hs[md+2],k,n,m,nn,mm;
unsigned long long f[2][600000];long long ans,g[2][600000];
char ch[mxn];
int tot[2],mi[mxn];//f:state g:sum
void prework(){
	for(int i=1;i<=max(n,m);i++)
		mi[i]=i<<1;
}
void put(unsigned long long cur,long long val){
	int s=cur%md;
	while(hs[s]){
		if(f[k][hs[s]]==cur){
			g[k][hs[s]]+=val;
			return;
		}
		s++;if(s==md)	s=0;
	}
	hs[s]=++tot[k];f[k][hs[s]]=cur;g[k][hs[s]]=val;
}
void solve(){
	tot[0]=1;g[0][1]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			k^=1;tot[k]=0;
			memset(hs,0,sizeof(hs));
			memset(f[k],0,sizeof(f[k]));
			memset(g[k],0,sizeof(g[k]));
			for(int u=1;u<=tot[k^1];u++){
				unsigned long long state=f[k^1][u];
				long long val=g[k^1][u];
				int p=(state>>mi[j-1])%4,q=(state>>mi[j])%4;
				if(!mp[i][j]){
					if(!p&&!q)	put(state,val);
				}else{
					if(!p){
						if(!q){
							if(mp[i+1][j]&&mp[i][j+1])
								put(state+1*(1<<mi[j-1])+2*(1<<mi[j]),val);
						}
						if(q==1){
							if(mp[i][j+1])	put(state,val);
							if(mp[i+1][j])	put(state-q*(1<<mi[j])+q*(1<<mi[j-1]),val);
						}
						if(q==2){
							if(mp[i][j+1])	put(state,val);
							if(mp[i+1][j])	put(state-q*(1<<mi[j])+q*(1<<mi[j-1]),val);
						}
					}
					if(p==1){
						if(!q){
							if(mp[i+1][j])	put(state,val);
							if(mp[i][j+1])	put(state-p*(1<<mi[j-1])+p*(1<<mi[j]),val);
						}
						if(q==1){
							int cur=1,s;
							for(int v=j+1;v<=m;v++){
								s=(state>>mi[v])%4;
								if(s==1)	cur++;
								if(s==2)	cur--;
								if(!cur){
									s=state-p*(1<<mi[j-1])-q*(1<<mi[j])-(1<<mi[v]);
									break;
								}
							}
							put(s,val);
						}
						if(q==2){
							if(i==nn&&j==mm)	ans+=val;
						}
					}
					if(p==2){
						if(!q){
							if(mp[i+1][j])	put(state,val);
							if(mp[i][j+1])	put(state-p*(1<<mi[j-1])+p*(1<<mi[j]),val);
						}
						if(q==1){
							put(state-p*(1<<mi[j-1])-q*(1<<mi[j]),val);
						}
						if(q==2){
							int cur=1,s;
							for(int v=j-2;v>=1;v--){
								s=(state>>mi[v])%4;
								if(s==2)	cur++;
								if(s==1)	cur--;
								if(!cur){
									s=state-p*(1<<mi[j-1])-q*(1<<mi[j])+(1<<mi[v]);
									break;
								}
							}
							put(s,val);
						}
					}
				}
			}
		}
		for (int j=1;j<=tot[k];j++)
            f[k][j]=f[k][j]<<2;
	}
}
void work(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",ch);
		for(int j=0;j<m;j++)
			if(ch[j]=='.')	mp[i][j+1]=1,nn=i,mm=j+1;
	}
	prework();
	solve();
	printf("%lld\n",ans);
}
int main(){ 
	work();
	return 0;
}

例题

HDU1565 方格取数(1)

思路:基础模板题,放一张图就知道了:

插头dp概念图
C o d e Code Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=21;
int dp[2][1<<N],n,v;

void solve(){
	memset(dp,0,sizeof(dp));
    int pre=0,now=1;
    dp[pre][0]=0;
    int ans=0;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
        	scanf("%d",&v);
            for(int S=0;S<(1<<n);S++){//轮廓线状态 
            	int newS=S&(~(1<<j));
				dp[now][newS]=max(dp[now][newS],dp[pre][S]);
                if((S&(1<<j))==0&&(j==0||(S&(1<<(j-1)))==0))//上面和左均为0,可以取数 
                    dp[now][S|(1<<j)]=max(dp[now][S|(1<<j)],dp[pre][S]+v);
            }
            swap(pre,now);
        }
    }
    for(int S=0;S<(1<<n);S++)
        ans=max(ans,dp[pre][S]);
    printf("%d\n",ans);
}

int main(){
    while(~scanf("%d",&n)){
        solve();
    }
    return 0;
}

原题: HDU-1693 Eat the Trees

题目翻译:

我们大多数人都知道,在名为DotA(古代防御)的游戏中, 帕吉在游戏的第一阶段是一个强大的英雄。但是,当游戏结束时,帕吉不再是一个强大的英雄。 因此,帕吉(Pudge)的队友为他分配了新任务-吃树! 这些树的大小为N * M个矩形,每个单元要么只有一棵树,要么根本没有。帕吉要做的就是吃掉牢房里的所有树木。 Pudge必须遵循以下几条规则: I.帕吉必须选择回路来吃掉树木,然后他才能吃掉所选回路中的所有树木。 二。不包含树的单元格无法访问,例如通过Pudge选择的通过回路的每个单元必须包含一棵树,并且当选择回路时,回路中单元中的树将消失。 三,帕吉人可以选择一个或多个回路来吃树。 现在帕吉有一个问题,那里有几种吃树的方法? 在下面的图片中,给出了N = 6和M = 3的三个样本(灰色正方形表示单元中没有树,黑色粗线表示所选的电路)

思路:

题解思路
C o d e Code Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long dp[2][1<<12];
long long ans; 
int n,m,v;

void solve(){
	int total=1<<(m+1);
	int pre=0,now=1;
    memset(dp[pre],0,sizeof(dp[pre]));
    dp[pre][0]=1;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            scanf("%d",&v);
            memset(dp[now],0,sizeof(dp[now]));
            int j0=1<<j;
            int j1=j0<<1;
            for(int S=0;S<total;S++){
            	bool p=S&j0,q=S&j1;//前一个格子的左,上状态 
				if(v==0){//障碍物,不可行 
					if(!p&&!q)
						dp[now][S]+=dp[pre][S];
				}else{
					if(p^q)//有一个为1,一个为0
						dp[now][S]+=dp[pre][S];//原状态不变
					dp[now][S^j0^j1]+=dp[pre][S];//相反状态
				}
			}
			swap(pre,now);//处理完一个格子后交换 
        }
        memset(dp[now],0,sizeof(dp[now]));//为处理下一行做准备 
        for(int S=0;S<total/2;S++)//最后的状态最大0111...1
            dp[now][S<<1]=dp[pre][S];//也可以+=,速度更快15ms,=,46ms 
        swap(pre,now);//交换后的pre是处理过的结果,为下一行做准备 
    }
    ans=dp[now][0];
}

int main(){
    int T,cas=1;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
		solve();
        printf("Case %d: There are %I64d ways to eat the trees.\n",cas++,ans);
    }
    return 0;
}

原题:POJ-1739 Tony’s Tour

题目翻译:

一个正方形城镇已被划分为n * m(n行和m列)平方图(1 <= N,M <= 8),其中一些被阻塞,其他未被阻塞。农场位于左下图,市场位于右下图。托尼(Tony)沿着每一个畅通无阻的地块走了一次,从农场到集市进行了整个小镇之旅。 编写一个程序,计算Betsy从农场到市场可以带多少次独特的旅行。

思路:

思路
C o d e Code Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
LL dp[2][1<<20];//记录方案数 
int state[2][1<<20];//记录状态,S=state[pre][s],pre为前一个格子标记,s为状态编号,S为状态 
int total[2];//记录状态总数 
int pre,now;
int endx,endy;//记录最后一个非障碍格子 
bool map[15][15];
char str[200];
int m,n;
LL ans;
const int HASH=4001;//坑点!!哈希值太大会超时!本题m<=10,用4位数素数,如3007,5位素数时间更多  
int Hash[HASH];//记录S对应的哈希值x的状态编号
 
void HashIn(int S,LL num){
	int x=S%HASH;
	while(~Hash[x]&&state[now][Hash[x]]!=S){//线性探测 
		x++;
		x%=HASH;
	}
	if(Hash[x]==-1){//未找到,加入hash表中 
		dp[now][total[now]]=num;
		state[now][total[now]]=S;
		Hash[x]=total[now];//记录状态编号
		total[now]++;
	}
	else//找到,累加方案数 
		dp[now][Hash[x]]+=num;
}
 
void init(){
	memset(map,0,sizeof(map));
	endx=-1;
	for(int i=0;i<n;i++){
		scanf("%s",str);
		for(int j=0;j<m;j++){
			if(str[j]=='.'){
				map[i][j]=1;
				endx=i;
				endy=j;
			}
			else
				map[i][j]=0;
		}
	}
	if(map[n-1][0]==0||map[n-1][m-1]==0)//最后一行的左角或右角不可达
		endx=endy=-1;
	else{
		endx=n+1;
		endy=m-1;
	}
	for(int j=0;j<m;j++){//增加两行,第一行首尾可行,其它不可行,第二行均可行 
		map[n][j]=0;
		map[n+1][j]=1;
	}
	map[n][0]=map[n][m-1]=1;
	n+=2;//注意  这里将矩形扩大  方便后面的使用
}
 
//位运算,取S按长度l的第p位
int getV(int S,int p,int l=2){//4进制,l=2;8进制,l=3
	return (S>>(p*l))&((1<<l)-1);
}
 
//位运算,设置S按长度l的第p位值为v
void setV(int& S,int p,int v,int l=2){
	S^=getV(S,p)<<(p*l);//第p位置0
	S|=v<<(p*l);//第p位置v 
}
 
void memsetnow(){//哈希每次用后清空 
	memset(Hash,-1,sizeof(Hash));
	total[now]=0;
}
 
void solve(){
	init();
	if(endx==-1){
		puts("0");
		return;
	}
	pre=0,now=1;
	ans=0;
	memsetnow();
	dp[pre][0]=1;
	state[pre][0]=0;
	total[pre]=1;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			memsetnow();
			for(int s=0;s<total[pre];s++){//s为状态编号 
				if(dp[pre][s])
				{
					LL num=dp[pre][s];
					int S=state[pre][s];
					int p=getV(S,j);
					int q=getV(S,j+1);
					if(map[i][j]==0){//有障碍 
						if(p==0&&q==0)
							HashIn(S,num);
						continue;
					}
					if(p==0&&q==0){//p、q均为0,第一种情况 
						if(map[i+1][j]&&map[i][j+1]){
							int nS=S;
							setV(nS,j,1);
							setV(nS,j+1,2);
							HashIn(nS,num);
						}
						continue;
					}
					if((p>0)^(q>0)){//p、q有一个为0,第二种情况 
						if(map[i+(p>0)][j+(q>0)])
							HashIn(S,num);
						if(map[i+(q>0)][j+(p>0)]){
							int nS=S;
							setV(nS,j,q);//p、q交换 
							setV(nS,j+1,p);
							HashIn(nS,num);
						}
						continue;
					}
					if(p==1&&q==1){//第三种情况,3.1 
						int find=1;
						for(int v=j+2;v<=m;v++){//向后搜q匹配的右括号),改为左括号 
							int k=getV(S,v);
							if(k==1)
								find++;
							else if(k==2)
								find--;
							if(find==0){
								int nS=S;
								setV(nS,j,0);//p、q置0 
								setV(nS,j+1,0);
								setV(nS,v,1);//改为左括号
								HashIn(nS,num);
								break;
							}
						}
						continue;
					}
					if(p==2&&q==2){//第三种情况,3.2 
						int find=1;
						for(int v=j-1;v>=0;v--){//向前搜p匹配的左括号(,改为右括号 
							int k=getV(S,v);
							if(k==2)
								find++;
							else if(k==1)
								find--;
							if(find==0){
								int nS=S;
								setV(nS,j,0);//p、q置0 
								setV(nS,j+1,0);
								setV(nS,v,2);//改为右括号
								HashIn(nS,num);
								break;
							}
						}
						continue;
					}
					if(p==2&&q==1){//第三种情况,3.3 
						int nS=S;
						setV(nS,j,0);//p、q置0 
						setV(nS,j+1,0);
						HashIn(nS,num);
						continue;
					}
					if(p==1&&q==2){//第三种情况,3.4 
						if(i==endx&&j==endy)//最后一个非障碍格子 
							ans+=num;
					}
				}	
			}
			swap(now,pre);
		}
		memsetnow();
		for(int s=0;s<total[pre];s++)
			if(dp[pre][s]){
				LL num=dp[pre][s];
				int S=state[pre][s]<<2;//左移一格 
				HashIn(S,num);
			}
		swap(now,pre);
	}
	printf("%I64d\n",ans);
}
 
int main(){
	while(~scanf("%d%d",&n,&m),n+m){
		if(n==1&&m==1){//只有一格特殊处理
	        scanf("%s",str);
	        if(str[0]=='.') printf("1\n");
	        else printf("0\n");
	        continue;
	    }else if(m==1){//只有一列多行的情况
	        int ok=1;
	        for(int i=0;i<n;i++){
				scanf("%s",str);
	            if(str[0]=='.'&&i<n-1)
	                ok=0;
	        }
	        if(str[0]=='#')
	            ok=0;
	        printf("%d\n",ok);
	        continue;
		}
		solve();
	} 
	return 0;
}

原题:URAL-1519 Formula 1

题目翻译:

谁会足够聪明地制定电路规划并让城市免受不可避免的耻辱?当然,只有真正的专业人员-本地技术大学一线团队中经过战斗的程序员!..但是我们的英雄们并没有在寻找轻松的生活,而是提出了更加困难的问题:“当然,如果我们找到了,我们的市长会很高兴的。有多少种构建电路的方法!” - 他们说。 应该说,沃洛格达州的赛道将非常简单。这将是一个大小为N * M的矩形单元,每个单元都构建一个单个电路段。每个线段应平行于矩形的一侧,因此电路上只能有直角的弯曲。在下面的图片中,给出了两个样本,其中N = M = 4(灰色正方形表示地鼠孔,粗黑线表示竞赛电路)。这里没有其他方法可以构建电路。
题目示意图

思路:

题目思路
C o d e Code Code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define LL long long
LL dp[2][1<<24];//记录方案数 
int state[2][1<<24];//记录状态,S=state[pre][s],pre为前一个格子标记,s为状态编号,S为状态 
int total[2];//记录状态总数 
int pre,now;
int endx,endy;//记录最后一个非障碍格子 
bool map[15][15];
int m,n;
LL ans;
const int HASH=40001;//坑点!!哈希值太小会超时!4位素数超时,m<=12,用5位数素数,如30007 
int Hash[HASH];//记录S对应的哈希值x的状态编号
 
void HashIn(int S,LL num){
	int x=S%HASH;
	while(~Hash[x]&&state[now][Hash[x]]!=S){//线性探测 
		x++;
		x%=HASH;
	}
	if(Hash[x]==-1){//未找到,加入hash表中 
		dp[now][total[now]]=num;
		state[now][total[now]]=S;
		Hash[x]=total[now];//记录状态编号
		total[now]++;
	}
	else//找到,累加方案数 
		dp[now][Hash[x]]+=num;
}
 
void init(){
	memset(map,0,sizeof(map));
	endx=-1;
	for(int i=0;i<n;i++){
		char str[200];
		scanf("%s",str);
		for(int j=0;j<m;j++){
			if(str[j]=='*')
				map[i][j]=0;
			else if(str[j]=='.'){
				map[i][j]=1;
				endx=i;
				endy=j;
			}
		}
	}
}
 
//位运算,取S按长度l的第p位
int getV(int S,int p,int l=2){//4进制,l=2;8进制,l=3
	return (S>>(p*l))&((1<<l)-1);
}
 
//位运算,设置S按长度l的第p位值为v
void setV(int& S,int p,int v,int l=2){
	S^=getV(S,p)<<(p*l);//第p位置0
	S|=v<<(p*l);//第p位置v 
}
 
void memsetnow(){//哈希表清空
	memset(Hash,-1,sizeof(Hash));
	total[now]=0;
}
 
void solve()
{
	init();
	if(endx==-1){
		puts("0");
		return;
	}
	pre=0,now=1;
	ans=0;
	memsetnow();//哈希表清空
	dp[pre][0]=1;
	state[pre][0]=0;
	total[pre]=1;
	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++){
			memsetnow();//哈希表清空
			for(int s=0;s<total[pre];s++){
				if(dp[pre][s]){
					LL num=dp[pre][s];
					int S=state[pre][s];
					int p=getV(S,j);
					int q=getV(S,j+1);
					if(map[i][j]==0){//有障碍,第一种情况 
						if(p==0&&q==0)
							HashIn(S,num);
						continue;
					}
					if(p==0&&q==0){//p、q均为0,第二种情况 
						if(map[i+1][j]&&map[i][j+1]){
							int nS=S;
							setV(nS,j,1);
							setV(nS,j+1,2);
							HashIn(nS,num);
						}
						continue;
					}
					if((p>0)^(q>0)){//p、q有一个为0,第三种情况 
						if(map[i+(p>0)][j+(q>0)])
							HashIn(S,num);
						if(map[i+(q>0)][j+(p>0)]){
							int nS=S;
							setV(nS,j,q);//p、q交换 
							setV(nS,j+1,p);
							HashIn(nS,num);
						}
						continue;
					}
					if(p==1&&q==1){//第四种情况,4.1 
						int find=1;
						for(int v=j+2;v<=m;v++){//向后搜q匹配的右括号),改为左括号 
							int k=getV(S,v);
							if(k==1)
								find++;
							else if(k==2)
								find--;
							if(find==0){
								int nS=S;
								setV(nS,j,0);//p、q置0 
								setV(nS,j+1,0);
								setV(nS,v,1);//改为左括号
								HashIn(nS,num);
								break;
							}
						}
						continue;
					}
					if(p==2&&q==2){//第四种情况,4.2 
						int find=1;
						for(int v=j-1;v>=0;v--){//向前搜p匹配的左括号(,改为右括号 
							int k=getV(S,v);
							if(k==2)
								find++;
							else if(k==1)
								find--;
							if(find==0){
								int nS=S;
								setV(nS,j,0);//p、q置0 
								setV(nS,j+1,0);
								setV(nS,v,2);//改为右括号
								HashIn(nS,num);
								break;
							}
						}
						continue;
					}
					if(p==2&&q==1){//第四种情况,4.3 
						int nS=S;
						setV(nS,j,0);//p、q置0 
						setV(nS,j+1,0);
						HashIn(nS,num);
						continue;
					}
					if(p==1&&q==2){//第四种情况,4.4 
						if(i==endx&&j==endy)//最后一个非障碍格子 
							ans+=num;
					}
				}	
			}
			swap(now,pre);
		}
		memsetnow();//哈希表清空
		for(int s=0;s<total[pre];s++)
			if(dp[pre][s]){
				LL num=dp[pre][s];
				int S=state[pre][s]<<2;//左移一格,四进制,一格用两位表示 
				HashIn(S,num);
			}
		swap(now,pre);
	}
	printf("%I64d\n",ans);
}
 
int main(){
	while(~scanf("%d%d",&n,&m)){
		solve();
	}
	return 0;
}
posted @ 2020-07-01 14:05  小罐猹  阅读(255)  评论(0编辑  收藏  举报