• 插头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
    ",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
    ",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.
    ",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
    ",ans);
    }
     
    int main(){
    	while(~scanf("%d%d",&n,&m),n+m){
    		if(n==1&&m==1){//只有一格特殊处理
    	        scanf("%s",str);
    	        if(str[0]=='.') printf("1
    ");
    	        else printf("0
    ");
    	        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
    ",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
    ",ans);
    }
     
    int main(){
    	while(~scanf("%d%d",&n,&m)){
    		solve();
    	}
    	return 0;
    }
    
    她透过我的血,看到了另一抹殷红
  • 相关阅读:
    .NET技术对软件行业的积极作用
    ADO.NET Entityframework MYSQL provider
    用杯子量水问题通用解法
    详解.NET异步
    说 框架、架构、模式、重构
    谈 三层结构与MVC模式的区别
    面试题一例分享
    LINQ notes2 lambda expression
    毕业论文B.3 DTW算法
    LINQ notes1 intro
  • 原文地址:https://www.cnblogs.com/zhangbeini/p/13771258.html
Copyright © 2020-2023  润新知